@intentic/sandbox-contract 1.250.0 → 1.252.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/contracts/ci.contract.d.ts +4 -0
- package/dist/contracts/ci.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +7 -0
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/ids/conversation-ids.d.ts +15 -0
- package/dist/ids/conversation-ids.d.ts.map +1 -1
- package/dist/ids/conversation-ids.js +30 -0
- package/dist/ids/conversation-ids.js.map +1 -1
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/schemas/ci.d.ts +4 -0
- package/dist/schemas/ci.d.ts.map +1 -1
- package/dist/schemas/ci.js +4 -0
- package/dist/schemas/ci.js.map +1 -1
- package/dist/schemas/devices.d.ts +19 -0
- package/dist/schemas/devices.d.ts.map +1 -1
- package/dist/schemas/devices.js +20 -1
- package/dist/schemas/devices.js.map +1 -1
- package/dist/state/fix-attempt-plan.d.ts +24 -0
- package/dist/state/fix-attempt-plan.d.ts.map +1 -0
- package/dist/state/fix-attempt-plan.js +22 -0
- package/dist/state/fix-attempt-plan.js.map +1 -0
- package/dist/state/fix-stance.d.ts +11 -0
- package/dist/state/fix-stance.d.ts.map +1 -0
- package/dist/state/fix-stance.js +79 -0
- package/dist/state/fix-stance.js.map +1 -0
- package/package.json +4 -4
- package/src/ids/conversation-ids.test.ts +61 -1
- package/src/ids/conversation-ids.ts +58 -1
- package/src/index.ts +2 -0
- package/src/schemas/ci.ts +8 -0
- package/src/schemas/devices.ts +38 -1
- package/src/state/fix-attempt-plan.test.ts +64 -0
- package/src/state/fix-attempt-plan.ts +54 -0
- package/src/state/fix-stance.test.ts +99 -0
- package/src/state/fix-stance.ts +140 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { AgentSummary } from "../schemas/agents.js";
|
|
2
|
+
|
|
3
|
+
/* WHAT BECAME OF THE AGENT A SURFACE SENT AFTER A FAILURE — a red pipeline row, a refused push — read off the fleet
|
|
4
|
+
* summary that surface joined by conversation id (ids/conversation-ids.ts). Reads both status and attention, since
|
|
5
|
+
* a parked turn is `idle` with a flag rather than finished. Mirrors the fleet board's own labels except for naming
|
|
6
|
+
* the subject (`Fix ready`), because on these surfaces the failure is what's read. Pure: no host, no clock.
|
|
7
|
+
*
|
|
8
|
+
* IN THE CONTRACT, NOT IN ONE SURFACE, because the words must not differ between the pipelines board (an
|
|
9
|
+
* extension) and the push question (the shell): the same agent, one click apart, described two ways is a bug a
|
|
10
|
+
* reader notices before a test does. Only the reading lives here; how each kind is DRAWN (icon, colour) is the UI
|
|
11
|
+
* kit's `fixStanceLook`, which an extension cannot load in a test and this package must not know about. */
|
|
12
|
+
|
|
13
|
+
export type FixStanceKind =
|
|
14
|
+
// A turn is in flight. Nothing is owed by the reader.
|
|
15
|
+
| "working"
|
|
16
|
+
// Parked on the user: a question, a plan, a permission, a setup, a conflict.
|
|
17
|
+
| "needs-you"
|
|
18
|
+
// The fix is written and held on the agent's branch, waiting to be reviewed and landed.
|
|
19
|
+
| "ready"
|
|
20
|
+
// It is in the workspace. What is left is proving it: re-run the check.
|
|
21
|
+
| "landed"
|
|
22
|
+
// Stopped on a spent allowance, which is a wait rather than a failure and must not be drawn as one.
|
|
23
|
+
| "waiting"
|
|
24
|
+
// Over, with nothing held: it failed, it was interrupted, it was stopped, or it changed nothing.
|
|
25
|
+
| "ended";
|
|
26
|
+
|
|
27
|
+
export interface FixStance {
|
|
28
|
+
readonly kind: FixStanceKind;
|
|
29
|
+
// True while this agent is still the failure's live answer; false once ended or landed.
|
|
30
|
+
readonly ongoing: boolean;
|
|
31
|
+
// True only for an ended fix: the one state where continuing it is a press rather than a chip.
|
|
32
|
+
readonly retry: boolean;
|
|
33
|
+
readonly label: string;
|
|
34
|
+
// Why the chip says what it says, and what pressing it does; no time-relative words.
|
|
35
|
+
readonly hint: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const needsYou = (label: string, hint: string): FixStance => ({ kind: "needs-you", ongoing: true, retry: false, label, hint });
|
|
39
|
+
|
|
40
|
+
// Ranks parked flags like the fleet does: setup outranks a plain question since it blocks the agent outright.
|
|
41
|
+
const ATTENTION: readonly { readonly flag: keyof AgentSummary["attention"]; readonly label: string; readonly why: string }[] = [
|
|
42
|
+
{ flag: "plan", label: "Approval needed", why: "it has proposed a plan and is waiting for a yes" },
|
|
43
|
+
{ flag: "capability", label: "Setup needed", why: "it needs something connected that is not connected yet" },
|
|
44
|
+
{ flag: "question", label: "Question for you", why: "it has asked you something" },
|
|
45
|
+
{ flag: "permission", label: "Permission needed", why: "it wants to use a tool it needs permission for" },
|
|
46
|
+
{ flag: "conflict", label: "Land conflict", why: "its work cannot be merged until somebody resolves a clash" },
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
// Live turn: running, unwinding a Stop, or waiting on a daemon repair; `awaiting` is handled above.
|
|
50
|
+
const IN_FLIGHT: ReadonlySet<AgentSummary["status"]> = new Set(["running", "resuming", "stopping", "dismissing"]);
|
|
51
|
+
|
|
52
|
+
const WORKING: FixStance = {
|
|
53
|
+
kind: "working",
|
|
54
|
+
ongoing: true,
|
|
55
|
+
retry: false,
|
|
56
|
+
label: "Agent working",
|
|
57
|
+
hint: "An agent is already working on this failure. Open the conversation to watch it.",
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const READY: FixStance = {
|
|
61
|
+
kind: "ready",
|
|
62
|
+
ongoing: true,
|
|
63
|
+
retry: false,
|
|
64
|
+
label: "Fix ready",
|
|
65
|
+
hint: "The fix is written and held on the agent's branch. Open it to review the diff and land it.",
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const LANDED: FixStance = {
|
|
69
|
+
kind: "landed",
|
|
70
|
+
ongoing: false,
|
|
71
|
+
retry: false,
|
|
72
|
+
label: "Fix landed",
|
|
73
|
+
hint: "The fix is in your workspace. Running the check again is what proves it.",
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const WAITING: FixStance = {
|
|
77
|
+
kind: "waiting",
|
|
78
|
+
ongoing: true,
|
|
79
|
+
retry: false,
|
|
80
|
+
label: "Waiting",
|
|
81
|
+
hint: "The fix agent's allowance is spent. Nothing is owed: the turn goes again when the provider's window reopens.",
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// True only for a clean finish (`ready`, or `idle` with a diff); a crashed turn with files also has a diff, but that
|
|
85
|
+
// belongs in the ending's hint, not here.
|
|
86
|
+
const holdingWork = (agent: AgentSummary): boolean =>
|
|
87
|
+
agent.status === "ready" || (agent.status === "idle" && agent.diff !== undefined && agent.diff.files > 0);
|
|
88
|
+
|
|
89
|
+
// Each ending's label states the reader's next move, not just what happened; `idle` counts too, since finishing with no
|
|
90
|
+
// changed files fixed nothing.
|
|
91
|
+
const ENDINGS: Partial<Record<AgentSummary["status"], { readonly label: string; readonly why: string }>> = {
|
|
92
|
+
error: { label: "Agent failed", why: "The fix agent's turn failed" },
|
|
93
|
+
interrupted: { label: "Interrupted", why: "The fix agent's turn was cut off when the sandbox went away" },
|
|
94
|
+
stopped: { label: "Stopped", why: "The fix agent was stopped before it finished" },
|
|
95
|
+
idle: { label: "Nothing changed", why: "The fix agent finished without changing any files" },
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const ended = (agent: AgentSummary): FixStance => {
|
|
99
|
+
const ending = ENDINGS[agent.status] ?? { label: "Agent stopped", why: "The fix agent's turn ended" };
|
|
100
|
+
// File count left behind by a turn that died mid-edit; not a fix, but worth surfacing.
|
|
101
|
+
const files = agent.diff?.files ?? 0;
|
|
102
|
+
const partial = files === 0 ? "" : ` It left ${files} changed file${files === 1 ? "" : "s"} on its branch.`;
|
|
103
|
+
return {
|
|
104
|
+
kind: "ended",
|
|
105
|
+
ongoing: false,
|
|
106
|
+
retry: true,
|
|
107
|
+
label: ending.label,
|
|
108
|
+
// Includes the provider's own failure sentence when present; often the only record of an unwatched run.
|
|
109
|
+
hint: `${ending.why}${agent.failure === undefined ? "" : `: ${agent.failure}`}.${partial} Continuing carries on in the same conversation; starting over opens a fresh one.`,
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// List order is priority, so a new status is inserted at its rank rather than appended as a trailing if. A spent
|
|
114
|
+
// allowance is checked first, ahead of `error`, since it is not a failure and must not offer a retry that spends the
|
|
115
|
+
// same allowance again.
|
|
116
|
+
const RULES: readonly ((agent: AgentSummary) => FixStance | undefined)[] = [
|
|
117
|
+
(agent) => (agent.status === "error" && agent.failureCode === "rate_limit" ? WAITING : undefined),
|
|
118
|
+
(agent) => {
|
|
119
|
+
const parked = ATTENTION.find((entry) => agent.attention[entry.flag]);
|
|
120
|
+
return parked === undefined ? undefined : needsYou(parked.label, `The fix agent is waiting on you: ${parked.why}.`);
|
|
121
|
+
},
|
|
122
|
+
// Bare `awaiting`/`conflict` with no flag raised: parked, with nothing more specific to say.
|
|
123
|
+
(agent) =>
|
|
124
|
+
agent.status === "awaiting" || agent.status === "conflict"
|
|
125
|
+
? needsYou("Needs you", "The fix agent has stopped and is waiting on you.")
|
|
126
|
+
: undefined,
|
|
127
|
+
(agent) => (IN_FLIGHT.has(agent.status) ? WORKING : undefined),
|
|
128
|
+
(agent) => (holdingWork(agent) ? READY : undefined),
|
|
129
|
+
(agent) => (agent.status === "landed" ? LANDED : undefined),
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
export const fixStance = (agent: AgentSummary): FixStance => {
|
|
133
|
+
for (const rule of RULES) {
|
|
134
|
+
const stance = rule(agent);
|
|
135
|
+
if (stance !== undefined) {
|
|
136
|
+
return stance;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return ended(agent);
|
|
140
|
+
};
|