setup-agent 0.0.6 → 0.0.7
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 +3 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +107 -8
- package/dist/cli.js.map +1 -1
- package/dist/client.js +1 -2
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +4 -2
- package/dist/host.js.map +1 -1
- package/dist/presets.js +1 -2
- package/dist/protocol.d.ts +9 -2
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +1 -2
- package/dist/protocol.js.map +1 -1
- package/package.json +3 -3
- package/src/cli.test.ts +403 -0
- package/src/cli.ts +205 -4
- package/src/host.test.ts +104 -0
- package/src/host.ts +18 -0
- package/src/protocol.ts +9 -3
package/src/host.test.ts
CHANGED
|
@@ -163,6 +163,70 @@ describe("CheckoutHost", () => {
|
|
|
163
163
|
});
|
|
164
164
|
|
|
165
165
|
describe("plans", () => {
|
|
166
|
+
it.each([
|
|
167
|
+
{ decision: "approve" },
|
|
168
|
+
{ decision: "revise", feedback: "Use pnpm" },
|
|
169
|
+
] satisfies Checkout.PlanDecision[])(
|
|
170
|
+
"rejects a $decision decision for a superseded revision",
|
|
171
|
+
async (decision) => {
|
|
172
|
+
const host = mount();
|
|
173
|
+
await host.commands["checkout/create"](seed);
|
|
174
|
+
await host.commands["checkout/begin-plan"]();
|
|
175
|
+
await host.commands["agent/plan"]({ markdown: "Install chat" });
|
|
176
|
+
const pending = { ...decision, id: seed.id, revision: 1 };
|
|
177
|
+
await host.commands["agent/plan"]({ markdown: "Replace the API route" });
|
|
178
|
+
|
|
179
|
+
expect(await reason(host.commands["checkout/plan"](pending))).toEqual({
|
|
180
|
+
reason: "stale-plan",
|
|
181
|
+
});
|
|
182
|
+
expect(currentPlan(host.state)).toMatchObject({
|
|
183
|
+
revision: 2,
|
|
184
|
+
status: "proposed",
|
|
185
|
+
});
|
|
186
|
+
expect(host.state.status).toBe("planning");
|
|
187
|
+
expect(currentPlan(host.state)?.feedback).toBeUndefined();
|
|
188
|
+
|
|
189
|
+
await host.commands["checkout/plan"]({ ...pending, revision: 2 });
|
|
190
|
+
expect(currentPlan(host.state)?.status).toBe(
|
|
191
|
+
decision.decision === "approve" ? "approved" : "changes-requested",
|
|
192
|
+
);
|
|
193
|
+
},
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
it("rejects a decision from an earlier setup with the same plan revision", async () => {
|
|
197
|
+
const host = mount();
|
|
198
|
+
await host.commands["checkout/create"](seed);
|
|
199
|
+
await host.commands["checkout/begin-plan"]();
|
|
200
|
+
await host.commands["agent/plan"]({ markdown: "Install chat" });
|
|
201
|
+
await host.commands["checkout/cancel"]({ id: seed.id });
|
|
202
|
+
await host.commands["checkout/create"]({ ...seed, id: "replacement" });
|
|
203
|
+
await host.commands["checkout/begin-plan"]();
|
|
204
|
+
await host.commands["agent/plan"]({ markdown: "Install cloud" });
|
|
205
|
+
|
|
206
|
+
expect(
|
|
207
|
+
await reason(
|
|
208
|
+
host.commands["checkout/plan"]({
|
|
209
|
+
decision: "approve",
|
|
210
|
+
id: seed.id,
|
|
211
|
+
revision: 1,
|
|
212
|
+
}),
|
|
213
|
+
),
|
|
214
|
+
).toEqual({ reason: "stale-plan" });
|
|
215
|
+
expect(currentPlan(host.state)).toMatchObject({
|
|
216
|
+
revision: 1,
|
|
217
|
+
status: "proposed",
|
|
218
|
+
});
|
|
219
|
+
expect(host.state.status).toBe("planning");
|
|
220
|
+
|
|
221
|
+
await host.commands["checkout/plan"]({
|
|
222
|
+
decision: "approve",
|
|
223
|
+
id: "replacement",
|
|
224
|
+
revision: 1,
|
|
225
|
+
});
|
|
226
|
+
expect(currentPlan(host.state)?.status).toBe("approved");
|
|
227
|
+
expect(host.state.status).toBe("installing");
|
|
228
|
+
});
|
|
229
|
+
|
|
166
230
|
it("refuses steps until the user approves a plan", async () => {
|
|
167
231
|
const host = mount();
|
|
168
232
|
await host.commands["checkout/create"](seed);
|
|
@@ -606,6 +670,46 @@ describe("inputs", () => {
|
|
|
606
670
|
describe("model inputs", () => {
|
|
607
671
|
const answer = { provider: "openai", model: "gpt-test" };
|
|
608
672
|
|
|
673
|
+
it("stores a valid redeem hash only on model inputs", async () => {
|
|
674
|
+
const host = mount();
|
|
675
|
+
await host.commands["checkout/create"](seed);
|
|
676
|
+
await host.commands["checkout/begin-plan"]();
|
|
677
|
+
const hash = "a".repeat(64);
|
|
678
|
+
const { inputId } = await host.commands["agent/ask"]({
|
|
679
|
+
...presetInput("llm-provider"),
|
|
680
|
+
redeemKeyHash: hash,
|
|
681
|
+
});
|
|
682
|
+
expect(
|
|
683
|
+
host.state.inputs.find((input) => input.id === inputId)?.redeemKeyHash,
|
|
684
|
+
).toBe(hash);
|
|
685
|
+
await host.commands["checkout/answer"]({
|
|
686
|
+
inputId,
|
|
687
|
+
answer: JSON.stringify(answer),
|
|
688
|
+
});
|
|
689
|
+
expect(
|
|
690
|
+
host.state.inputs.find((input) => input.id === inputId)?.redeemKeyHash,
|
|
691
|
+
).toBe(hash);
|
|
692
|
+
for (const redeemKeyHash of [
|
|
693
|
+
"A".repeat(64),
|
|
694
|
+
"a".repeat(63),
|
|
695
|
+
"not-a-hash",
|
|
696
|
+
]) {
|
|
697
|
+
expect(
|
|
698
|
+
await reason(
|
|
699
|
+
host.commands["agent/ask"]({
|
|
700
|
+
...presetInput("llm-provider"),
|
|
701
|
+
redeemKeyHash,
|
|
702
|
+
}),
|
|
703
|
+
),
|
|
704
|
+
).toEqual({ reason: "invalid-input" });
|
|
705
|
+
}
|
|
706
|
+
expect(
|
|
707
|
+
await reason(
|
|
708
|
+
host.commands["agent/ask"]({ prompt: "Name?", redeemKeyHash: hash }),
|
|
709
|
+
),
|
|
710
|
+
).toEqual({ reason: "invalid-input" });
|
|
711
|
+
});
|
|
712
|
+
|
|
609
713
|
it("parses a model answer and rejects malformed ones", () => {
|
|
610
714
|
expect(parseModelAnswer(JSON.stringify(answer))).toEqual(answer);
|
|
611
715
|
expect(
|
package/src/host.ts
CHANGED
|
@@ -86,6 +86,12 @@ const useCheckoutHost = (restored: unknown) => {
|
|
|
86
86
|
|
|
87
87
|
const ask = (seed: Checkout.InputSeed & { stepId?: string }) => {
|
|
88
88
|
const kind = seed.kind ?? "text";
|
|
89
|
+
if (
|
|
90
|
+
seed.redeemKeyHash !== undefined &&
|
|
91
|
+
(kind !== "model" || !/^[0-9a-f]{64}$/.test(seed.redeemKeyHash))
|
|
92
|
+
) {
|
|
93
|
+
throw reject("invalid-input", "invalid model redeem key hash");
|
|
94
|
+
}
|
|
89
95
|
const options =
|
|
90
96
|
kind === "entry-point"
|
|
91
97
|
? parseEntryPointOptions(seed.options)
|
|
@@ -166,6 +172,9 @@ const useCheckoutHost = (restored: unknown) => {
|
|
|
166
172
|
...(kind === "product" && { product: seed.product }),
|
|
167
173
|
...(seed.default !== undefined && { default: seed.default }),
|
|
168
174
|
...(seed.help !== undefined && { help: seed.help }),
|
|
175
|
+
...(seed.redeemKeyHash !== undefined && {
|
|
176
|
+
redeemKeyHash: seed.redeemKeyHash,
|
|
177
|
+
}),
|
|
169
178
|
optional: seed.optional ?? false,
|
|
170
179
|
status: "open",
|
|
171
180
|
...(stepId !== undefined && { stepId }),
|
|
@@ -331,6 +340,15 @@ const useCheckoutHost = (restored: unknown) => {
|
|
|
331
340
|
if (plan === undefined) {
|
|
332
341
|
throw reject("no-plan", "the agent has not proposed a plan yet");
|
|
333
342
|
}
|
|
343
|
+
if (
|
|
344
|
+
(params.id !== undefined && params.id !== state.id) ||
|
|
345
|
+
(params.revision !== undefined && params.revision !== plan.revision)
|
|
346
|
+
) {
|
|
347
|
+
throw reject(
|
|
348
|
+
"stale-plan",
|
|
349
|
+
"review the current setup plan before deciding",
|
|
350
|
+
);
|
|
351
|
+
}
|
|
334
352
|
if (plan.status !== "proposed") {
|
|
335
353
|
throw reject("plan-decided", `the plan is already ${plan.status}`);
|
|
336
354
|
}
|
package/src/protocol.ts
CHANGED
|
@@ -101,6 +101,7 @@ export namespace Checkout {
|
|
|
101
101
|
product?: string;
|
|
102
102
|
default?: string;
|
|
103
103
|
help?: InputHelp;
|
|
104
|
+
redeemKeyHash?: string;
|
|
104
105
|
optional: boolean;
|
|
105
106
|
status: InputStatus;
|
|
106
107
|
answer?: string;
|
|
@@ -192,12 +193,16 @@ export namespace Checkout {
|
|
|
192
193
|
product?: string;
|
|
193
194
|
default?: string;
|
|
194
195
|
help?: InputHelp;
|
|
196
|
+
redeemKeyHash?: string;
|
|
195
197
|
optional?: boolean;
|
|
196
198
|
};
|
|
197
199
|
|
|
198
|
-
export type PlanDecision =
|
|
199
|
-
|
|
200
|
-
|
|
200
|
+
export type PlanDecision = {
|
|
201
|
+
/** The setup the browser reviewed; omitted by legacy clients. */
|
|
202
|
+
id?: string;
|
|
203
|
+
/** The plan revision the browser reviewed; omitted by legacy clients. */
|
|
204
|
+
revision?: number;
|
|
205
|
+
} & ({ decision: "approve" } | { decision: "revise"; feedback: string });
|
|
201
206
|
|
|
202
207
|
export type Commands = {
|
|
203
208
|
"checkout/create": (params: {
|
|
@@ -254,6 +259,7 @@ export namespace Checkout {
|
|
|
254
259
|
| "plan-required"
|
|
255
260
|
| "no-plan"
|
|
256
261
|
| "plan-decided"
|
|
262
|
+
| "stale-plan"
|
|
257
263
|
| "finish-not-proposed"
|
|
258
264
|
| "feedback-required"
|
|
259
265
|
| "empty-plan"
|