@tangle-network/agent-app 0.43.10 → 0.43.12
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/.claude/skills/eval-campaign/SKILL.md +2 -2
- package/.claude/skills/improve-conductor/SKILL.md +1 -1
- package/.claude/skills/measurement-validation/SKILL.md +1 -1
- package/.claude/skills/skill-evolution/SKILL.md +2 -2
- package/.claude/skills/surface-evolution/SKILL.md +2 -2
- package/dist/assistant/index.d.ts +47 -3
- package/dist/assistant/index.js +125 -44
- package/dist/assistant/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
} from '@tangle-network/agent-app/eval-campaign'
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
> Requires `@tangle-network/agent-eval >= 0.
|
|
39
|
+
> Requires `@tangle-network/agent-eval >= 0.100.0` (peer; current published is 0.107.0). The scaffold composes the substrate downward; never import a product package from agent-eval (layering rule).
|
|
40
40
|
|
|
41
41
|
## Minimal wiring (copy, then fill the three blanks)
|
|
42
42
|
|
|
@@ -89,7 +89,7 @@ if (result.gate.decision === 'ship') await ship(result.winnerSurface)
|
|
|
89
89
|
| `baselineSurface` | — (required) | the surface the loop optimizes; start `''` |
|
|
90
90
|
| `mutationPrimitives` | gepaProposer's own | your optimization levers (additive directives) |
|
|
91
91
|
| `proposer` | `gepaProposer` | pass `evolutionaryProposer({ mutator })` for blind addendum rotation |
|
|
92
|
-
| `gate` | `defaultProductionGate` (Δ 0.05) | `paretoSignificanceGate` for multi-objective; tune `deltaThreshold` for your rubric scale |
|
|
92
|
+
| `gate` | `defaultProductionGate` (Δ 0.05) | `paretoSignificanceGate` for multi-objective; tune `deltaThreshold` for your rubric scale . To prove a held-out lift is real CONTENT and not just added prompt/mount FOOTPRINT, compose `neutralizationGate` (agent-eval >= 0.107.0, from `@tangle-network/agent-eval/campaign`) — a footprint-matched placebo arm. It needs `ctx.neutralizedJudgeScores` from `runImprovementLoop({ neutralize })`; `selfImprove`/this scaffold do not surface that option yet, so wire it at the `runImprovementLoop` level or re-export it in the scaffold first. |
|
|
93
93
|
| `budget` | 3 gens × pop 2, 0.25 holdout | `budget.reps` (replicates → tighter CIs), `budget.promoteTopK`, `budget.holdoutScenarios` (explicit split), `budget.dollars` (cost cap) |
|
|
94
94
|
| `expectUsage` | **`'assert'`** | the fail-loud backend-integrity guard. Leave at `'assert'` for real runs (a stub cell throws); set `'off'` ONLY for a deterministic offline/replay run |
|
|
95
95
|
| `labeledStore` | off | capture every artifact + judge score (the dataset you ship + few-shot corpus); set `captureSource` (default `'eval-run'`) |
|
|
@@ -11,7 +11,7 @@ You delegate the building to an agent holding `eval-architect` + `surface-evolut
|
|
|
11
11
|
|
|
12
12
|
## Invariant (non-negotiable)
|
|
13
13
|
|
|
14
|
-
1. **Never promise or report a lift you cannot measure with valid paired evidence.** Surface the honest verdict: `ship` / `hold` / `need-more-data` / `invalid`. "Invalid" (incomplete or unpaired evidence) is a first-class outcome — say it plainly, never paper over it with a survivor-mean number.
|
|
14
|
+
1. **Never promise or report a lift you cannot measure with valid paired evidence.** Surface the honest verdict: `ship` / `hold` / `need-more-data` / `invalid`. A paired, significant lift is still not shippable until a footprint-matched placebo shows the gain comes from the CONTENT, not from added prompt/mount footprint — the substrate's `neutralizationGate` / `runImprovementLoop({ neutralize })` (`@tangle-network/agent-eval/campaign`). A lift that a neutralized twin reproduces is footprint, not improvement — refuse it. Route this check through `measurement-validation`. "Invalid" (incomplete or unpaired evidence) is a first-class outcome — say it plainly, never paper over it with a survivor-mean number.
|
|
15
15
|
2. **Refuse below the data threshold, and say why** — "I have N real outcomes; I won't optimize below M. Here's how to get to M." A refusal with a reason builds more trust than a fabricated win.
|
|
16
16
|
3. **Route correctly.** Improvable by surface-tuning → dispatch `surface-evolution`. Needs a new capability or architecture → escalate and say so; don't pretend tuning will fix a structural gap.
|
|
17
17
|
4. **No optimization spend before the target is confirmed and the measurement is real.** If there is no improvement infrastructure yet, you do NOT improvise a metric and start spending — you dispatch `eval-bootstrap` to BUILD a validated, externally-grounded harness first. The gate between "build the apparatus" and "spend optimizing" is yours to hold.
|
|
@@ -13,7 +13,7 @@ Held by both the orchestrator (`improve-conductor`) and the builder (`eval-archi
|
|
|
13
13
|
|
|
14
14
|
1. **Refuse to optimize if CV(metric) > the target delta.** If the run-to-run noise is bigger than the effect you're paying to move, the metric *cannot* validate the change — raise reps or fix the metric first. Do not tune against noise.
|
|
15
15
|
2. **Refuse to report a lift over INCOMPLETE or UNPAIRED evidence.** Every held-out scenario must have a non-errored cell on *both* the baseline and the candidate side. Below the paired-n floor (≥3), the run is **invalid**, not a verdict. A lift computed over survivors is worse than no number.
|
|
16
|
-
> **Enforced by** `trustVerdicts` from `@tangle-network/agent-app/eval-campaign`
|
|
16
|
+
> **Enforced by** `trustVerdicts` from `@tangle-network/agent-app/eval-campaign` (rater-trust dimension: IRR floor + per-item rater spread, within-item never pooled, + survivor floor, each failure named in `trustReasons`), composed with the agent-eval statistical gates from `@tangle-network/agent-eval/campaign`: `powerPreflight` (before-gate for Invariant 1 — refuse to greenlight spend when the metric is underpowered vs the target delta), `heldOutGate` / `heldoutSignificance` (paired-bootstrap CI over the held-out split — the after-gate that would have caught the +47 by refusing an unpaired lift), and `neutralizationGate` + `neutralizeText` (footprint-matched placebo gate — a held-out lift that does not survive `neutralizeText` came from added prompt/mount FOOTPRINT, not content, and must not be believed).
|
|
17
17
|
3. **Every metric ties to a product-value claim** — "if this number moves, *this* user-visible outcome moves with it." No claim → it's a proxy → don't optimize it.
|
|
18
18
|
4. **Below the data threshold of real outcomes, refuse to optimize** — state N and say why. You cannot improve what you have not yet observed enough of.
|
|
19
19
|
|
|
@@ -24,7 +24,7 @@ Each skill's *judgment* surface is itself an evolvable surface, optimized by the
|
|
|
24
24
|
|
|
25
25
|
> *Did following this skill produce an eval that yielded real held-out lift, with no critical-dimension regression?*
|
|
26
26
|
|
|
27
|
-
Above a data threshold of real runs, the skill proposes revisions to its own judgment — gated identically (held-out, critical-dimension floor, paired-n ≥ floor). **Invariants are the frozen surface; judgment is the evolvable surface.** A skill improving itself is just `surface-evolution` pointed inward.
|
|
27
|
+
Above a data threshold of real runs, the skill proposes revisions to its own judgment — gated identically (held-out, critical-dimension floor, paired-n ≥ floor, and a footprint-matched placebo — the neutralization gate — that proves the lift came from content, not from added prompt/mount footprint). **Invariants are the frozen surface; judgment is the evolvable surface.** A skill improving itself is just `surface-evolution` pointed inward.
|
|
28
28
|
|
|
29
29
|
## The north-star: the agent builder, closed-loop
|
|
30
30
|
|
|
@@ -39,4 +39,4 @@ The fleet — **legal, tax, gtm, creative, insurance** — is the training distr
|
|
|
39
39
|
- A skill that lists steps but has **no self-test** — you can't tell if following it worked.
|
|
40
40
|
- An "invariant" that's really a **judgment call in disguise** — over-constraining; it should live in Judgment so the agent can adapt it per product.
|
|
41
41
|
- A judgment surface with **no evolves-by hook** — it will rot, and nothing will notice.
|
|
42
|
-
- A reported lift with **no held-out or no paired-n** — the slot machine. This is the one that ends the product.
|
|
42
|
+
- A reported lift with **no held-out or no paired-n, or no footprint-matched placebo (neutralization)** — the slot machine. This is the one that ends the product.
|
|
@@ -7,12 +7,12 @@ description: Optimize ONE evolvable surface (a prompt section / tool config) aga
|
|
|
7
7
|
|
|
8
8
|
You are a closed-loop controller for agent quality. **Sensor** = the eval (built by `eval-architect`, certified by `measurement-validation`). **Controller** = the proposer that proposes surface rewrites. **Actuator** = promotion (writing the surface to the live agent). **Safety interlock** = the gate. The interlock is the entire point: it prefers *under-promotion* to Goodhart. A loop that ships every apparent gain is worthless; a loop that ships only evidence-backed gains is the product.
|
|
9
9
|
|
|
10
|
-
The engine exists in the substrate (`@tangle-network/agent-eval/contract` `selfImprove` / `runImprovementLoop`, `gepaProposer`, `defaultProductionGate`) — re-exported via `@tangle-network/agent-app/eval-campaign`. **Do not rebuild it.** This skill is how you wire and run it safely.
|
|
10
|
+
The engine exists in the substrate (`@tangle-network/agent-eval/contract` `selfImprove` / `runImprovementLoop`, `gepaProposer`, `defaultProductionGate`, `neutralizationGate`, `composeGate`) — re-exported via `@tangle-network/agent-app/eval-campaign`. **Do not rebuild it.** This skill is how you wire and run it safely.
|
|
11
11
|
|
|
12
12
|
## Invariant (non-negotiable)
|
|
13
13
|
|
|
14
14
|
1. **Optimize exactly ONE surface that production renders identically.** The artifact you mutate offline must be the artifact the live agent loads — one source, rendered both places (e.g. an evolvable prompt section materialized from a single file into the live system prompt). If offline and online diverge, the lift is fictional the moment it ships.
|
|
15
|
-
2. **Gate promotion on a held-out split AND a critical-dimension floor.** Never promote a net composite gain that regresses a guarded dimension (safety, hallucination, the regulated invariant). A +10 composite that loses 30 on hallucination is a regression, not a win.
|
|
15
|
+
2. **Gate promotion on a held-out split AND a critical-dimension floor.** Never promote a net composite gain that regresses a guarded dimension (safety, hallucination, the regulated invariant). A +10 composite that loses 30 on hallucination is a regression, not a win. **AND gate on a footprint placebo.** A held-out lift proves the candidate beat baseline; it does NOT prove the lift came from the surface's CONTENT rather than the extra prompt/mount FOOTPRINT it added. Compose `neutralizationGate` (from `@tangle-network/agent-eval/campaign`, wired via `runImprovementLoop({ neutralize })`) AFTER the significance gate — `composeGate(heldOutGate({...}), neutralizationGate({...}))` — so a candidate whose content-blanked, footprint-matched twin reproduces more than `maxDecorativeFraction` (default 0.5) of the lift is HELD as decorative, no matter how large or significant its raw lift.
|
|
16
16
|
3. **Budget is a hard ceiling and cost-aware** — skip cells beyond the ceiling, never abort. The user's spend maps to generations × candidates × reps: $0.20 buys one quick generation; $50 buys a wide search with tight CIs.
|
|
17
17
|
4. **Never evolve a frozen surface.** The regulated invariants — human-in-the-loop, the compliance gate, auth/RBAC — are off-limits. Declare exactly what is evolvable; everything else the loop must not touch.
|
|
18
18
|
|
|
@@ -82,6 +82,19 @@ interface ConnectionRequirement {
|
|
|
82
82
|
* app slug); the card then shows the requirement without a connect link. */
|
|
83
83
|
connectUrl?: string | null;
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Outcome of a host-driven, in-place connect for a single proposal requirement
|
|
87
|
+
* (see {@link AssistantDockProps.onConnectRequirement}). The host owns the whole
|
|
88
|
+
* connect experience — an OAuth popup, an api-key entry modal, an app install,
|
|
89
|
+
* and any error surfacing — and the assistant stays agnostic to how it happens:
|
|
90
|
+
* the host reports back only whether the requirement is now satisfied, which the
|
|
91
|
+
* card uses to flip that requirement to connected without a reload. `connected:
|
|
92
|
+
* false` (a failed or cancelled connect) leaves the card unchanged so the user
|
|
93
|
+
* can retry.
|
|
94
|
+
*/
|
|
95
|
+
interface ConnectRequirementResult {
|
|
96
|
+
connected: boolean;
|
|
97
|
+
}
|
|
85
98
|
interface ToolProposalEventData {
|
|
86
99
|
/** Null only if the server has no proposal store wired (tools then unusable). */
|
|
87
100
|
proposalId: string | null;
|
|
@@ -428,6 +441,17 @@ interface UseAssistantChatOptions {
|
|
|
428
441
|
* signal the platform used.
|
|
429
442
|
*/
|
|
430
443
|
onWorkflowMutation?: () => void;
|
|
444
|
+
/**
|
|
445
|
+
* Called when the user activates a proposal requirement's connect affordance.
|
|
446
|
+
* The host runs whatever connect flow it owns — an OAuth popup, an api-key
|
|
447
|
+
* modal, an app install — and resolves whether the requirement is now
|
|
448
|
+
* satisfied; the card then flips that requirement to connected. Host-agnostic:
|
|
449
|
+
* the assistant never learns how the connection is made, so any host (platform,
|
|
450
|
+
* sandbox, intelligence, …) can supply its own. When omitted, the card falls
|
|
451
|
+
* back to navigating the requirement's connect target (see
|
|
452
|
+
* {@link ProposalCardProps.navigate}).
|
|
453
|
+
*/
|
|
454
|
+
onConnectRequirement?: (requirement: ConnectionRequirement) => Promise<ConnectRequirementResult>;
|
|
431
455
|
}
|
|
432
456
|
interface AssistantChat {
|
|
433
457
|
state: AssistantState;
|
|
@@ -441,6 +465,14 @@ interface AssistantChat {
|
|
|
441
465
|
stop: () => void;
|
|
442
466
|
confirm: (proposal: PendingProposal) => Promise<void>;
|
|
443
467
|
cancel: (proposal: PendingProposal) => void;
|
|
468
|
+
/** True when the host supplied an in-place connect handler
|
|
469
|
+
* ({@link UseAssistantChatOptions.onConnectRequirement}); the proposal card
|
|
470
|
+
* uses this to offer in-place connect instead of navigating away. */
|
|
471
|
+
canConnectRequirement: boolean;
|
|
472
|
+
/** Run the host's in-place connect for one of a proposal's requirements and,
|
|
473
|
+
* on success, flip that requirement to connected on the card. Resolves (a
|
|
474
|
+
* no-op) when no host handler is set or the connect didn't complete. */
|
|
475
|
+
connectRequirement: (proposal: PendingProposal, requirement: ConnectionRequirement) => Promise<void>;
|
|
444
476
|
reset: () => void;
|
|
445
477
|
/** Open an existing thread from history, loading its transcript. */
|
|
446
478
|
switchThread: (threadId: string) => void;
|
|
@@ -518,6 +550,12 @@ interface AssistantDockProps {
|
|
|
518
550
|
renderGraph?: (yaml: string) => ReactNode;
|
|
519
551
|
/** Called after a workflow-mutating tool is confirmed (host re-fetches its list). */
|
|
520
552
|
onWorkflowMutation?: () => void;
|
|
553
|
+
/** In-place connect handler for a proposal's integration requirements. The host
|
|
554
|
+
* runs its own connect flow (OAuth popup, api-key modal, app install) and
|
|
555
|
+
* resolves whether the requirement is now satisfied; the proposal card then
|
|
556
|
+
* flips it to connected. Host-agnostic — when omitted, the card falls back to
|
|
557
|
+
* navigating the requirement's connect target via `navigate`. */
|
|
558
|
+
onConnectRequirement?: (requirement: ConnectionRequirement) => Promise<ConnectRequirementResult>;
|
|
521
559
|
/** Markdown renderer for assistant message content (plain text when absent). */
|
|
522
560
|
renderMarkdown?: (content: string) => ReactNode;
|
|
523
561
|
/** Per-tool custom detail renderers for expanded tool cards in the transcript. */
|
|
@@ -527,7 +565,7 @@ interface AssistantDockProps {
|
|
|
527
565
|
* transport, and proposal flow stay owned by the panel. */
|
|
528
566
|
renderTranscript?: (view: AssistantTranscriptView) => ReactNode;
|
|
529
567
|
}
|
|
530
|
-
declare function AssistantDock({ userId, navigate, balanceUsd, formatMoney, renderGraph, onWorkflowMutation, renderMarkdown, toolRenderers, renderTranscript, }: AssistantDockProps): react.JSX.Element;
|
|
568
|
+
declare function AssistantDock({ userId, navigate, balanceUsd, formatMoney, renderGraph, onWorkflowMutation, onConnectRequirement, renderMarkdown, toolRenderers, renderTranscript, }: AssistantDockProps): react.JSX.Element;
|
|
531
569
|
|
|
532
570
|
interface AssistantPanelProps {
|
|
533
571
|
chat: AssistantChat;
|
|
@@ -608,11 +646,17 @@ interface ProposalCardProps {
|
|
|
608
646
|
onCancel: () => void;
|
|
609
647
|
/** Host navigation for connect targets / the integrations page. */
|
|
610
648
|
navigate?: (path: string) => void;
|
|
649
|
+
/** In-place connect handler for a requirement. When provided, a requirement's
|
|
650
|
+
* connect affordance calls this (showing a busy state) instead of navigating
|
|
651
|
+
* to `connectUrl`/the integrations page — the host runs its own connect flow
|
|
652
|
+
* and the card flips the requirement to connected on success. Host-agnostic;
|
|
653
|
+
* wired by the panel from {@link UseAssistantChatOptions.onConnectRequirement}. */
|
|
654
|
+
onConnect?: (requirement: ConnectionRequirement) => void | Promise<void>;
|
|
611
655
|
/** Render the workflow YAML as a node graph (the `./workflows` WorkflowGraph).
|
|
612
656
|
* When absent, the YAML is shown as text. */
|
|
613
657
|
renderGraph?: (yaml: string) => ReactNode;
|
|
614
658
|
}
|
|
615
|
-
declare function ProposalCard({ proposal, confirming, onConfirm, onCancel, navigate, renderGraph, }: ProposalCardProps): react.JSX.Element;
|
|
659
|
+
declare function ProposalCard({ proposal, confirming, onConfirm, onCancel, navigate, onConnect, renderGraph, }: ProposalCardProps): react.JSX.Element;
|
|
616
660
|
|
|
617
661
|
interface AssistantLauncher {
|
|
618
662
|
/** Whether the assistant drawer is open. */
|
|
@@ -630,4 +674,4 @@ declare function AssistantLauncherProvider({ children, }: {
|
|
|
630
674
|
}): react.JSX.Element;
|
|
631
675
|
declare function useAssistantLauncher(): AssistantLauncher;
|
|
632
676
|
|
|
633
|
-
export { type AssistantChat, type AssistantClient, type AssistantClientConfig, AssistantClientInputError, AssistantClientProvider, type AssistantDeliveryMode, AssistantDock, type AssistantDockProps, type AssistantLauncher, AssistantLauncherProvider, type AssistantModelOption, type AssistantModels, type AssistantModelsResult, AssistantPanel, type AssistantPanelProps, type AssistantSendOptions, type AssistantStreamEvent, type AssistantThreadSummary, type AssistantThreads, AssistantTranscript, type AssistantTranscriptProps, type AssistantTranscriptView, type ChatMessage, type ChatRequest, type ChatRole, type ConfirmResult, type ConnectionRequirement, type ConnectionRequirementKind, type DeltaEventData, type DoneEventData, type ErrorEventData, type PendingProposal, ProposalCard, type ProposalCardProps, type ReasoningEventData, type ThreadEventData, type ThreadHistoryResult, type ToolActivityStatus, type ToolCallEventData, type ToolOutcome, type ToolProposalEventData, type ToolResultEventData, type UsageEventData, type UsageInfo, type UseAssistantChatOptions, adaptTranscript, assistantIsThinking, createAssistantClient, useAssistantChat, useAssistantClient, useAssistantLauncher, useAssistantModels, useAssistantThreads };
|
|
677
|
+
export { type AssistantChat, type AssistantClient, type AssistantClientConfig, AssistantClientInputError, AssistantClientProvider, type AssistantDeliveryMode, AssistantDock, type AssistantDockProps, type AssistantLauncher, AssistantLauncherProvider, type AssistantModelOption, type AssistantModels, type AssistantModelsResult, AssistantPanel, type AssistantPanelProps, type AssistantSendOptions, type AssistantStreamEvent, type AssistantThreadSummary, type AssistantThreads, AssistantTranscript, type AssistantTranscriptProps, type AssistantTranscriptView, type ChatMessage, type ChatRequest, type ChatRole, type ConfirmResult, type ConnectRequirementResult, type ConnectionRequirement, type ConnectionRequirementKind, type DeltaEventData, type DoneEventData, type ErrorEventData, type PendingProposal, ProposalCard, type ProposalCardProps, type ReasoningEventData, type ThreadEventData, type ThreadHistoryResult, type ToolActivityStatus, type ToolCallEventData, type ToolOutcome, type ToolProposalEventData, type ToolResultEventData, type UsageEventData, type UsageInfo, type UseAssistantChatOptions, adaptTranscript, assistantIsThinking, createAssistantClient, useAssistantChat, useAssistantClient, useAssistantLauncher, useAssistantModels, useAssistantThreads };
|
package/dist/assistant/index.js
CHANGED
|
@@ -1029,6 +1029,25 @@ function assistantReducer(state, action) {
|
|
|
1029
1029
|
status: pendingProposals.length > 0 ? "awaiting_confirm" : state.status === "awaiting_confirm" ? "idle" : state.status
|
|
1030
1030
|
};
|
|
1031
1031
|
}
|
|
1032
|
+
case "requirement_connected": {
|
|
1033
|
+
const kind = action.kind ?? "integration";
|
|
1034
|
+
let changed = false;
|
|
1035
|
+
const pendingProposals = state.pendingProposals.map((p) => {
|
|
1036
|
+
if (p.callId !== action.callId || !p.requirements) return p;
|
|
1037
|
+
let rowChanged = false;
|
|
1038
|
+
const requirements = p.requirements.map((r) => {
|
|
1039
|
+
if (!r.connected && r.provider === action.provider && (r.kind ?? "integration") === kind) {
|
|
1040
|
+
rowChanged = true;
|
|
1041
|
+
return { ...r, connected: true };
|
|
1042
|
+
}
|
|
1043
|
+
return r;
|
|
1044
|
+
});
|
|
1045
|
+
if (!rowChanged) return p;
|
|
1046
|
+
changed = true;
|
|
1047
|
+
return { ...p, requirements };
|
|
1048
|
+
});
|
|
1049
|
+
return changed ? { ...state, pendingProposals } : state;
|
|
1050
|
+
}
|
|
1032
1051
|
case "stopped":
|
|
1033
1052
|
return {
|
|
1034
1053
|
...state,
|
|
@@ -1121,6 +1140,8 @@ function useAssistantChat(userId, options) {
|
|
|
1121
1140
|
clientRef.current = client;
|
|
1122
1141
|
const onWorkflowMutationRef = useRef(options?.onWorkflowMutation);
|
|
1123
1142
|
onWorkflowMutationRef.current = options?.onWorkflowMutation;
|
|
1143
|
+
const onConnectRequirementRef = useRef(options?.onConnectRequirement);
|
|
1144
|
+
onConnectRequirementRef.current = options?.onConnectRequirement;
|
|
1124
1145
|
const confirmingRef = useRef(/* @__PURE__ */ new Set());
|
|
1125
1146
|
const [confirmingIds, setConfirmingIds] = useState(EMPTY_IDS);
|
|
1126
1147
|
const sendingRef = useRef(false);
|
|
@@ -1362,6 +1383,29 @@ function useAssistantChat(userId, options) {
|
|
|
1362
1383
|
error: null
|
|
1363
1384
|
});
|
|
1364
1385
|
}, []);
|
|
1386
|
+
const connectRequirement = useCallback(
|
|
1387
|
+
async (proposal, requirement) => {
|
|
1388
|
+
const handler = onConnectRequirementRef.current;
|
|
1389
|
+
if (!handler) return;
|
|
1390
|
+
const seq = confirmSeqRef.current;
|
|
1391
|
+
let result;
|
|
1392
|
+
try {
|
|
1393
|
+
result = await handler(requirement);
|
|
1394
|
+
} catch {
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
if (confirmSeqRef.current !== seq) return;
|
|
1398
|
+
if (result?.connected) {
|
|
1399
|
+
dispatch({
|
|
1400
|
+
type: "requirement_connected",
|
|
1401
|
+
callId: proposal.callId,
|
|
1402
|
+
provider: requirement.provider,
|
|
1403
|
+
kind: requirement.kind
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
},
|
|
1407
|
+
[]
|
|
1408
|
+
);
|
|
1365
1409
|
const reset = useCallback(() => {
|
|
1366
1410
|
streamSeqRef.current += 1;
|
|
1367
1411
|
confirmSeqRef.current += 1;
|
|
@@ -1454,6 +1498,8 @@ function useAssistantChat(userId, options) {
|
|
|
1454
1498
|
stop,
|
|
1455
1499
|
confirm,
|
|
1456
1500
|
cancel,
|
|
1501
|
+
canConnectRequirement: Boolean(options?.onConnectRequirement),
|
|
1502
|
+
connectRequirement,
|
|
1457
1503
|
reset,
|
|
1458
1504
|
switchThread,
|
|
1459
1505
|
restoring
|
|
@@ -1599,13 +1645,13 @@ function useAssistantThreads(userId) {
|
|
|
1599
1645
|
// src/assistant/AssistantDock.tsx
|
|
1600
1646
|
import { MessageSquare } from "lucide-react";
|
|
1601
1647
|
import {
|
|
1602
|
-
useEffect as
|
|
1603
|
-
useRef as
|
|
1648
|
+
useEffect as useEffect7,
|
|
1649
|
+
useRef as useRef8
|
|
1604
1650
|
} from "react";
|
|
1605
1651
|
|
|
1606
1652
|
// src/assistant/AssistantPanel.tsx
|
|
1607
1653
|
import { History, MessageSquarePlus, Minus, Plus, X } from "lucide-react";
|
|
1608
|
-
import { useEffect as
|
|
1654
|
+
import { useEffect as useEffect6, useMemo as useMemo3, useRef as useRef6, useState as useState6 } from "react";
|
|
1609
1655
|
|
|
1610
1656
|
// src/assistant/AssistantHistory.tsx
|
|
1611
1657
|
import { Search, Trash2 } from "lucide-react";
|
|
@@ -1725,7 +1771,7 @@ function AssistantHistory({
|
|
|
1725
1771
|
}
|
|
1726
1772
|
|
|
1727
1773
|
// src/assistant/ProposalCard.tsx
|
|
1728
|
-
import { useState as useState4 } from "react";
|
|
1774
|
+
import { useEffect as useEffect4, useRef as useRef3, useState as useState4 } from "react";
|
|
1729
1775
|
|
|
1730
1776
|
// src/assistant/provider-label.ts
|
|
1731
1777
|
var PROVIDER_LABELS = {
|
|
@@ -1743,13 +1789,14 @@ function providerLabel(provider) {
|
|
|
1743
1789
|
}
|
|
1744
1790
|
|
|
1745
1791
|
// src/assistant/ProposalCard.tsx
|
|
1746
|
-
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1792
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1747
1793
|
function ProposalCard({
|
|
1748
1794
|
proposal,
|
|
1749
1795
|
confirming,
|
|
1750
1796
|
onConfirm,
|
|
1751
1797
|
onCancel,
|
|
1752
1798
|
navigate,
|
|
1799
|
+
onConnect,
|
|
1753
1800
|
renderGraph
|
|
1754
1801
|
}) {
|
|
1755
1802
|
const view = describeProposal(proposal);
|
|
@@ -1805,7 +1852,8 @@ function ProposalCard({
|
|
|
1805
1852
|
RequirementRow,
|
|
1806
1853
|
{
|
|
1807
1854
|
req: r,
|
|
1808
|
-
navigate
|
|
1855
|
+
navigate,
|
|
1856
|
+
onConnect
|
|
1809
1857
|
},
|
|
1810
1858
|
`${r.provider}-${r.kind ?? "integration"}`
|
|
1811
1859
|
)) }),
|
|
@@ -1855,14 +1903,41 @@ function openConnect(target, navigate) {
|
|
|
1855
1903
|
}
|
|
1856
1904
|
function RequirementRow({
|
|
1857
1905
|
req,
|
|
1858
|
-
navigate
|
|
1906
|
+
navigate,
|
|
1907
|
+
onConnect
|
|
1859
1908
|
}) {
|
|
1909
|
+
const [connecting, setConnecting] = useState4(false);
|
|
1910
|
+
const mountedRef = useRef3(true);
|
|
1911
|
+
useEffect4(() => {
|
|
1912
|
+
mountedRef.current = true;
|
|
1913
|
+
return () => {
|
|
1914
|
+
mountedRef.current = false;
|
|
1915
|
+
};
|
|
1916
|
+
}, []);
|
|
1860
1917
|
const label = providerLabel(req.provider);
|
|
1861
1918
|
const isApp = req.kind === "github_app";
|
|
1862
1919
|
const kindLabel = isApp ? `${label} App` : label;
|
|
1863
1920
|
const statusText = req.connected ? isApp ? "installed" : "connected" : isApp ? "not installed" : "not connected";
|
|
1864
|
-
const canConnect = !req.connected && req.connectUrl !== null;
|
|
1921
|
+
const canConnect = !req.connected && (Boolean(onConnect) || req.connectUrl !== null);
|
|
1865
1922
|
const target = req.connectUrl ?? "/app/integrations";
|
|
1923
|
+
const handleConnect = () => {
|
|
1924
|
+
if (!onConnect) {
|
|
1925
|
+
openConnect(target, navigate);
|
|
1926
|
+
return;
|
|
1927
|
+
}
|
|
1928
|
+
setConnecting(true);
|
|
1929
|
+
let pending;
|
|
1930
|
+
try {
|
|
1931
|
+
pending = onConnect(req);
|
|
1932
|
+
} catch {
|
|
1933
|
+
if (mountedRef.current) setConnecting(false);
|
|
1934
|
+
return;
|
|
1935
|
+
}
|
|
1936
|
+
void Promise.resolve(pending).catch(() => {
|
|
1937
|
+
}).finally(() => {
|
|
1938
|
+
if (mountedRef.current) setConnecting(false);
|
|
1939
|
+
});
|
|
1940
|
+
};
|
|
1866
1941
|
return /* @__PURE__ */ jsxs2("li", { className: "flex items-center justify-between gap-2 text-xs", children: [
|
|
1867
1942
|
/* @__PURE__ */ jsxs2("span", { className: "flex min-w-0 items-center gap-2", children: [
|
|
1868
1943
|
/* @__PURE__ */ jsx3(ProviderLogo, { provider: req.provider, size: 16 }),
|
|
@@ -1884,16 +1959,17 @@ function RequirementRow({
|
|
|
1884
1959
|
)
|
|
1885
1960
|
] })
|
|
1886
1961
|
] }),
|
|
1887
|
-
canConnect && /* @__PURE__ */
|
|
1962
|
+
canConnect && /* @__PURE__ */ jsx3(
|
|
1888
1963
|
"button",
|
|
1889
1964
|
{
|
|
1890
1965
|
type: "button",
|
|
1891
|
-
onClick:
|
|
1892
|
-
|
|
1893
|
-
|
|
1966
|
+
onClick: handleConnect,
|
|
1967
|
+
disabled: connecting,
|
|
1968
|
+
className: "shrink-0 text-primary disabled:opacity-50",
|
|
1969
|
+
children: connecting ? isApp ? "Installing\u2026" : "Connecting\u2026" : /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1894
1970
|
isApp ? "Install" : "Connect",
|
|
1895
1971
|
" \u2192"
|
|
1896
|
-
]
|
|
1972
|
+
] })
|
|
1897
1973
|
}
|
|
1898
1974
|
)
|
|
1899
1975
|
] });
|
|
@@ -1901,7 +1977,7 @@ function RequirementRow({
|
|
|
1901
1977
|
|
|
1902
1978
|
// src/assistant/transcript.tsx
|
|
1903
1979
|
import { useCallback as useCallback3, useMemo as useMemo2 } from "react";
|
|
1904
|
-
import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1980
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1905
1981
|
function assistantIsThinking(state) {
|
|
1906
1982
|
if (state.status !== "streaming") return false;
|
|
1907
1983
|
const streaming = state.streamingId ? state.messages.find((m) => m.id === state.streamingId) : void 0;
|
|
@@ -1999,7 +2075,7 @@ function ProposalSlot({
|
|
|
1999
2075
|
proposal,
|
|
2000
2076
|
render
|
|
2001
2077
|
}) {
|
|
2002
|
-
return /* @__PURE__ */ jsx4(
|
|
2078
|
+
return /* @__PURE__ */ jsx4(Fragment2, { children: render(proposal) });
|
|
2003
2079
|
}
|
|
2004
2080
|
function AssistantTranscript({
|
|
2005
2081
|
view,
|
|
@@ -2016,7 +2092,7 @@ function AssistantTranscript({
|
|
|
2016
2092
|
[renderMarkdown]
|
|
2017
2093
|
);
|
|
2018
2094
|
if (messages.length === 0 && !view.isStreaming) {
|
|
2019
|
-
return /* @__PURE__ */ jsx4(
|
|
2095
|
+
return /* @__PURE__ */ jsx4(Fragment2, { children: emptyState });
|
|
2020
2096
|
}
|
|
2021
2097
|
return /* @__PURE__ */ jsx4(
|
|
2022
2098
|
ChatMessages,
|
|
@@ -2026,7 +2102,7 @@ function AssistantTranscript({
|
|
|
2026
2102
|
agentLabel: "Assistant",
|
|
2027
2103
|
renderMarkdown: markdown,
|
|
2028
2104
|
toolRenderers,
|
|
2029
|
-
renderEmpty: () => /* @__PURE__ */ jsx4(
|
|
2105
|
+
renderEmpty: () => /* @__PURE__ */ jsx4(Fragment2, { children: emptyState }),
|
|
2030
2106
|
renderExtras: (message) => {
|
|
2031
2107
|
const proposals = message.id === proposalHostId && view.pendingProposals.length > 0 ? /* @__PURE__ */ jsx4("div", { className: "mt-3 flex flex-col gap-3", children: view.pendingProposals.map((proposal) => /* @__PURE__ */ jsx4(
|
|
2032
2108
|
ProposalSlot,
|
|
@@ -2041,7 +2117,7 @@ function AssistantTranscript({
|
|
|
2041
2117
|
" this turn"
|
|
2042
2118
|
] }) : null;
|
|
2043
2119
|
if (!proposals && !cost) return null;
|
|
2044
|
-
return /* @__PURE__ */ jsxs3(
|
|
2120
|
+
return /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
2045
2121
|
proposals,
|
|
2046
2122
|
cost
|
|
2047
2123
|
] });
|
|
@@ -2051,7 +2127,7 @@ function AssistantTranscript({
|
|
|
2051
2127
|
}
|
|
2052
2128
|
|
|
2053
2129
|
// src/assistant/usePanelPrefs.ts
|
|
2054
|
-
import { useCallback as useCallback4, useEffect as
|
|
2130
|
+
import { useCallback as useCallback4, useEffect as useEffect5, useRef as useRef4, useState as useState5 } from "react";
|
|
2055
2131
|
var MIN_PANEL_WIDTH = 360;
|
|
2056
2132
|
var DEFAULT_PANEL_WIDTH = 448;
|
|
2057
2133
|
var MAX_PANEL_WIDTH_FRACTION = 0.95;
|
|
@@ -2094,8 +2170,8 @@ function clampScale(value) {
|
|
|
2094
2170
|
function usePanelWidth() {
|
|
2095
2171
|
const [width, setWidthState] = useState5(DEFAULT_PANEL_WIDTH);
|
|
2096
2172
|
const [maxWidth, setMaxWidth] = useState5(() => maxPanelWidth());
|
|
2097
|
-
const desiredRef =
|
|
2098
|
-
|
|
2173
|
+
const desiredRef = useRef4(DEFAULT_PANEL_WIDTH);
|
|
2174
|
+
useEffect5(() => {
|
|
2099
2175
|
setMaxWidth(maxPanelWidth());
|
|
2100
2176
|
const stored = readNumber(WIDTH_KEY);
|
|
2101
2177
|
if (stored != null) {
|
|
@@ -2103,7 +2179,7 @@ function usePanelWidth() {
|
|
|
2103
2179
|
setWidthState(clampWidth(stored));
|
|
2104
2180
|
}
|
|
2105
2181
|
}, []);
|
|
2106
|
-
|
|
2182
|
+
useEffect5(() => {
|
|
2107
2183
|
const onResize = () => {
|
|
2108
2184
|
setMaxWidth(maxPanelWidth());
|
|
2109
2185
|
setWidthState(clampWidth(desiredRef.current));
|
|
@@ -2130,7 +2206,7 @@ function usePanelWidth() {
|
|
|
2130
2206
|
}
|
|
2131
2207
|
function useFontScale() {
|
|
2132
2208
|
const [scale, setScaleState] = useState5(DEFAULT_FONT_SCALE);
|
|
2133
|
-
|
|
2209
|
+
useEffect5(() => {
|
|
2134
2210
|
const stored = readNumber(FONT_SCALE_KEY);
|
|
2135
2211
|
if (stored != null) setScaleState(clampScale(stored));
|
|
2136
2212
|
}, []);
|
|
@@ -2153,7 +2229,7 @@ function useIsDesktop() {
|
|
|
2153
2229
|
const [isDesktop, setIsDesktop] = useState5(
|
|
2154
2230
|
() => typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(min-width: 768px)").matches : true
|
|
2155
2231
|
);
|
|
2156
|
-
|
|
2232
|
+
useEffect5(() => {
|
|
2157
2233
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
2158
2234
|
return;
|
|
2159
2235
|
}
|
|
@@ -2167,12 +2243,12 @@ function useIsDesktop() {
|
|
|
2167
2243
|
}
|
|
2168
2244
|
|
|
2169
2245
|
// src/assistant/use-stick-to-bottom.ts
|
|
2170
|
-
import { useCallback as useCallback5, useLayoutEffect, useRef as
|
|
2246
|
+
import { useCallback as useCallback5, useLayoutEffect, useRef as useRef5 } from "react";
|
|
2171
2247
|
var STICK_SLACK_PX = 48;
|
|
2172
2248
|
function useStickToBottom(ref, { enabled, contentSignature, streamingId, threadId }) {
|
|
2173
|
-
const stuckRef =
|
|
2174
|
-
const prevStreamingRef =
|
|
2175
|
-
const prevThreadRef =
|
|
2249
|
+
const stuckRef = useRef5(true);
|
|
2250
|
+
const prevStreamingRef = useRef5(streamingId);
|
|
2251
|
+
const prevThreadRef = useRef5(threadId);
|
|
2176
2252
|
const onScroll = useCallback5(() => {
|
|
2177
2253
|
if (!enabled) return;
|
|
2178
2254
|
const el = ref.current;
|
|
@@ -2250,24 +2326,24 @@ function AssistantPanel({
|
|
|
2250
2326
|
const threads = useAssistantThreads(userId);
|
|
2251
2327
|
const font = useFontScale();
|
|
2252
2328
|
const [view, setView] = useState6("chat");
|
|
2253
|
-
const historyButtonRef =
|
|
2254
|
-
const logRef =
|
|
2329
|
+
const historyButtonRef = useRef6(null);
|
|
2330
|
+
const logRef = useRef6(null);
|
|
2255
2331
|
const pickerModels = useMemo3(
|
|
2256
2332
|
() => toPickerModels(models, chat.selectedModel),
|
|
2257
2333
|
[models, chat.selectedModel]
|
|
2258
2334
|
);
|
|
2259
2335
|
const pickerValue = chat.selectedModel ?? models.default ?? "";
|
|
2260
2336
|
const { state } = chat;
|
|
2261
|
-
const chatRef =
|
|
2337
|
+
const chatRef = useRef6(chat);
|
|
2262
2338
|
chatRef.current = chat;
|
|
2263
|
-
|
|
2339
|
+
useEffect6(() => {
|
|
2264
2340
|
if (view !== "history") return;
|
|
2265
2341
|
const search = logRef.current?.querySelector(
|
|
2266
2342
|
'input[type="search"]'
|
|
2267
2343
|
);
|
|
2268
2344
|
(search ?? logRef.current)?.focus();
|
|
2269
2345
|
}, [view]);
|
|
2270
|
-
|
|
2346
|
+
useEffect6(() => {
|
|
2271
2347
|
if (view !== "history") return;
|
|
2272
2348
|
const onKeyDownCapture = (e) => {
|
|
2273
2349
|
if (e.key !== "Escape") return;
|
|
@@ -2317,6 +2393,7 @@ function AssistantPanel({
|
|
|
2317
2393
|
onConfirm: () => chat.confirm(proposal),
|
|
2318
2394
|
onCancel: () => chat.cancel(proposal),
|
|
2319
2395
|
navigate,
|
|
2396
|
+
onConnect: chat.canConnectRequirement ? (requirement) => chat.connectRequirement(proposal, requirement) : void 0,
|
|
2320
2397
|
renderGraph
|
|
2321
2398
|
}
|
|
2322
2399
|
);
|
|
@@ -2617,7 +2694,7 @@ function useAssistantLauncher() {
|
|
|
2617
2694
|
}
|
|
2618
2695
|
|
|
2619
2696
|
// src/assistant/ResizeHandle.tsx
|
|
2620
|
-
import { useRef as
|
|
2697
|
+
import { useRef as useRef7 } from "react";
|
|
2621
2698
|
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
2622
2699
|
function ResizeHandle({
|
|
2623
2700
|
width,
|
|
@@ -2626,7 +2703,7 @@ function ResizeHandle({
|
|
|
2626
2703
|
onCommit,
|
|
2627
2704
|
onNudge
|
|
2628
2705
|
}) {
|
|
2629
|
-
const dragRef =
|
|
2706
|
+
const dragRef = useRef7(null);
|
|
2630
2707
|
const onPointerDown = (e) => {
|
|
2631
2708
|
if (e.button !== 0) return;
|
|
2632
2709
|
e.preventDefault();
|
|
@@ -2695,7 +2772,7 @@ function ResizeHandle({
|
|
|
2695
2772
|
}
|
|
2696
2773
|
|
|
2697
2774
|
// src/assistant/AssistantDock.tsx
|
|
2698
|
-
import { Fragment as
|
|
2775
|
+
import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
2699
2776
|
function focusableWithin(container) {
|
|
2700
2777
|
const selector = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
2701
2778
|
return Array.from(container.querySelectorAll(selector)).filter(
|
|
@@ -2709,19 +2786,23 @@ function AssistantDock({
|
|
|
2709
2786
|
formatMoney,
|
|
2710
2787
|
renderGraph,
|
|
2711
2788
|
onWorkflowMutation,
|
|
2789
|
+
onConnectRequirement,
|
|
2712
2790
|
renderMarkdown,
|
|
2713
2791
|
toolRenderers,
|
|
2714
2792
|
renderTranscript
|
|
2715
2793
|
}) {
|
|
2716
2794
|
const { open, openAssistant, closeAssistant } = useAssistantLauncher();
|
|
2717
|
-
const chat = useAssistantChat(userId, {
|
|
2795
|
+
const chat = useAssistantChat(userId, {
|
|
2796
|
+
onWorkflowMutation,
|
|
2797
|
+
onConnectRequirement
|
|
2798
|
+
});
|
|
2718
2799
|
const isDesktop = useIsDesktop();
|
|
2719
2800
|
const { width, maxWidth, setWidth, previewWidth, nudgeWidth } = usePanelWidth();
|
|
2720
|
-
const launcherRef =
|
|
2721
|
-
const dialogRef =
|
|
2722
|
-
const returnFocusRef =
|
|
2723
|
-
const wasOpenRef =
|
|
2724
|
-
|
|
2801
|
+
const launcherRef = useRef8(null);
|
|
2802
|
+
const dialogRef = useRef8(null);
|
|
2803
|
+
const returnFocusRef = useRef8(null);
|
|
2804
|
+
const wasOpenRef = useRef8(false);
|
|
2805
|
+
useEffect7(() => {
|
|
2725
2806
|
if (!open) return;
|
|
2726
2807
|
const onKeyDown = (e) => {
|
|
2727
2808
|
if (e.key === "Escape") closeAssistant();
|
|
@@ -2729,7 +2810,7 @@ function AssistantDock({
|
|
|
2729
2810
|
document.addEventListener("keydown", onKeyDown);
|
|
2730
2811
|
return () => document.removeEventListener("keydown", onKeyDown);
|
|
2731
2812
|
}, [open, closeAssistant]);
|
|
2732
|
-
|
|
2813
|
+
useEffect7(() => {
|
|
2733
2814
|
if (open) {
|
|
2734
2815
|
if (!wasOpenRef.current && !returnFocusRef.current) {
|
|
2735
2816
|
returnFocusRef.current = document.activeElement;
|
|
@@ -2785,7 +2866,7 @@ function AssistantDock({
|
|
|
2785
2866
|
first.focus();
|
|
2786
2867
|
}
|
|
2787
2868
|
};
|
|
2788
|
-
return /* @__PURE__ */ jsxs5(
|
|
2869
|
+
return /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
2789
2870
|
/* @__PURE__ */ jsx8(
|
|
2790
2871
|
"div",
|
|
2791
2872
|
{
|