agent-golf 0.1.0 → 0.1.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/README.md +9 -7
- package/config.ts +1 -1
- package/package.json +5 -4
- package/problem-view.ts +74 -0
- package/problems.ts +9 -9
package/README.md
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
Pi commands for the Agent Golf coding-agent leaderboard. Login, browsing and submission never call a model or compute scores locally.
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**0.1.1:** uses https://agent-golf.csbasics.in and includes read-only interactive problem views. Local integration with the real counter is verified and the production catalogue is reachable. A fresh solved production submission has not been verified; follow your organizer's readiness announcement before competition use.
|
|
6
6
|
|
|
7
7
|
## Install and update
|
|
8
8
|
|
|
9
9
|
Requires Node.js 22.19+ and the Pi coding agent (tested with Pi 1.1.0).
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
pi install npm:agent-golf
|
|
12
|
+
pi install npm:agent-golf
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Unversioned installations follow `latest`. The `preview` channel also receives this URL update; existing preview users can keep that installation source.
|
|
16
16
|
|
|
17
17
|
Start a new Pi process, or run `/reload` in an existing one. To update an unpinned installation:
|
|
18
18
|
|
|
@@ -20,7 +20,9 @@ Start a new Pi process, or run `/reload` in an existing one. To update an unpinn
|
|
|
20
20
|
pi update --extensions
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
Restart Pi or `/reload` afterward. An exact version such as `npm:agent-golf@0.1.0` is pinned; updating does not advance it to a different version. The `npm:agent-golf@preview` installation follows the preview channel; it does not automatically switch to `latest`.
|
|
23
|
+
Restart Pi or `/reload` afterward. An exact version such as `npm:agent-golf@0.1.0` is pinned; updating does not advance it to a different version. Remove the old pinned source (`pi remove npm:agent-golf@0.1.0`), then install `npm:agent-golf@0.1.1` explicitly to replace that pin. The `npm:agent-golf@preview` installation follows the preview channel; it does not automatically switch to `latest`.
|
|
24
|
+
|
|
25
|
+
**Updating from 0.1.0:** the default server origin changed. Unset any old `AGENT_GOLF_API_BASE_URL` override, update/restart Pi, and run `/golf-login` with your student key valid on the new website. Old-origin credentials are preserved but never migrated or sent to the new origin.
|
|
24
26
|
|
|
25
27
|
## Commands
|
|
26
28
|
|
|
@@ -28,7 +30,7 @@ Restart Pi or `/reload` afterward. An exact version such as `npm:agent-golf@0.1.
|
|
|
28
30
|
|
|
29
31
|
Browse public contest rules, challenge URLs, instructions and each problem's model/thinking setup. Rules open first, followed by a problem selector. Close a detail view to return to the list; cancel the list to exit. An empty list still shows the rules.
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
Interactive Pi uses read-only, scrollable text views: Enter on rules opens the problem list, Esc closes browsing, and Enter/Esc on details returns to the list. Arrow keys, Page Up/Down and Home/End scroll; navigation hints use your configured confirm/cancel keys. RPC clients retain temporary text-editor dialogs with explicit navigation titles; all edits are discarded. Nothing is inserted into your prompt or session. `any` setup is displayed as "any model, one per session" or "any thinking level".
|
|
32
34
|
|
|
33
35
|
Browsing sends no key, cookie or session log, makes no model calls, and never starts/binds an attempt or changes your setup. The catalogue is advisory; server submission checks are authoritative. Replies are bounded to 64 KB and strings to 1,000 Unicode characters, with terminal-safety and exact Hackattic URL checks.
|
|
34
36
|
|
|
@@ -56,7 +58,7 @@ The server owns scores and status. A null total with `missing_usage` is displaye
|
|
|
56
58
|
|
|
57
59
|
## Server configuration
|
|
58
60
|
|
|
59
|
-
Default API origin: **https://agent-golf.
|
|
61
|
+
Default API origin: **https://agent-golf.csbasics.in**.
|
|
60
62
|
|
|
61
63
|
For a trusted local server:
|
|
62
64
|
|
|
@@ -75,7 +77,7 @@ Both authentication and browsing/submission use this origin. Only HTTPS and loop
|
|
|
75
77
|
- **No persisted session file:** submit from the saved attempt, not a fresh custom-only session that has not flushed to disk.
|
|
76
78
|
- **Upload/network error:** the upload may already have reached the server. Check the website before retrying. The plugin never retries automatically.
|
|
77
79
|
|
|
78
|
-
Removing the
|
|
80
|
+
Removing the package with `pi remove npm:agent-golf` (use the source you installed, e.g. `npm:agent-golf@preview`) does not remove your credential file. Delete that file separately only if you intend to discard saved logins.
|
|
79
81
|
|
|
80
82
|
## License
|
|
81
83
|
|
package/config.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-golf",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Zero-model-call Pi commands for Agent Golf login, raw session submission and problem browsing",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"homepage": "https://agent-golf.
|
|
6
|
+
"homepage": "https://agent-golf.csbasics.in",
|
|
7
7
|
"keywords": ["pi-package", "agent-golf"],
|
|
8
8
|
"engines": { "node": ">=22.19.0" },
|
|
9
|
-
"files": ["index.ts", "config.ts", "login.ts", "submit.ts", "user-bash.ts", "problems.ts", "README.md", "LICENSE"],
|
|
9
|
+
"files": ["index.ts", "config.ts", "login.ts", "submit.ts", "user-bash.ts", "problems.ts", "problem-view.ts", "README.md", "LICENSE"],
|
|
10
10
|
"pi": { "extensions": ["./index.ts"], "skills": [], "prompts": [], "themes": [] },
|
|
11
11
|
"publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" },
|
|
12
12
|
"type": "module",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"test": "node --test test/*.test.mjs"
|
|
15
15
|
},
|
|
16
16
|
"peerDependencies": {
|
|
17
|
-
"@earendil-works/pi-coding-agent": "*"
|
|
17
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
18
|
+
"@earendil-works/pi-tui": "*"
|
|
18
19
|
},
|
|
19
20
|
"devDependencies": {}
|
|
20
21
|
}
|
package/problem-view.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
type TuiModule = typeof import("@earendil-works/pi-tui");
|
|
4
|
+
type Factory = Parameters<ExtensionContext["ui"]["custom"]>[0];
|
|
5
|
+
type Tui = Parameters<Factory>[0];
|
|
6
|
+
type Theme = Parameters<Factory>[1];
|
|
7
|
+
type Bindings = Parameters<Factory>[2];
|
|
8
|
+
|
|
9
|
+
// Dependency injection keeps rendering tests independent of Pi's module loader.
|
|
10
|
+
export function createProblemView(
|
|
11
|
+
lib: TuiModule, tui: Tui, theme: Theme, bindings: Bindings,
|
|
12
|
+
done: (continued: boolean) => void, title: string, body: string, action: string, cancel: string,
|
|
13
|
+
) {
|
|
14
|
+
const text = new lib.Text(body, 0, 0);
|
|
15
|
+
const scroll = new lib.ScrollView(text, { scrollbar: "hidden", overscroll: "contain" });
|
|
16
|
+
let height = 1;
|
|
17
|
+
let closed = false;
|
|
18
|
+
const finish = (value: boolean) => { if (!closed) { closed = true; done(value); } };
|
|
19
|
+
const keys = (name: "tui.select.confirm" | "tui.select.cancel") => bindings.getKeys(name).join("/");
|
|
20
|
+
return {
|
|
21
|
+
render(width: number) {
|
|
22
|
+
const w = Math.max(1, Math.floor(width));
|
|
23
|
+
const confirmKey = keys("tui.select.confirm"), cancelKey = keys("tui.select.cancel");
|
|
24
|
+
const navigation = action === cancel ? `${confirmKey}/${cancelKey}: ${action}` :
|
|
25
|
+
`${confirmKey}: ${action} · ${cancelKey}: ${cancel}`;
|
|
26
|
+
const navigationLines = new lib.Text(navigation, 0, 0).render(w);
|
|
27
|
+
const available = Math.max(1, tui.terminal.rows - 5);
|
|
28
|
+
// Physically tiny terminals cannot fit body plus controls; ask for resize
|
|
29
|
+
// rather than presenting a clipped action as if it were the full help.
|
|
30
|
+
if (w < 12 || navigationLines.length + 2 > available) {
|
|
31
|
+
return [lib.truncateToWidth("Resize terminal", w),
|
|
32
|
+
...new lib.Text(`${confirmKey}/${cancelKey}`, 0, 0).render(w)].slice(0, available);
|
|
33
|
+
}
|
|
34
|
+
const showScrollHint = available >= navigationLines.length + 3;
|
|
35
|
+
height = Math.max(1, Math.min(16, available - 1 - navigationLines.length - Number(showScrollHint)));
|
|
36
|
+
const lines = text.render(w);
|
|
37
|
+
scroll.updateLayout(lines.length, height, () => tui.requestRender());
|
|
38
|
+
const visible = lines.slice(scroll.scrollTop, scroll.scrollTop + height);
|
|
39
|
+
const scrollHint = `${w < 45 ? "↑/↓ scroll" : "↑/↓ PgUp/PgDn Home/End: scroll"} · ${scroll.scrollTop + 1}-${Math.min(lines.length, scroll.scrollTop + height)}/${lines.length}`;
|
|
40
|
+
return [
|
|
41
|
+
lib.truncateToWidth(theme.fg("accent", title), w),
|
|
42
|
+
...visible,
|
|
43
|
+
...navigationLines.map((line) => theme.fg("muted", line)),
|
|
44
|
+
...(showScrollHint ? [lib.truncateToWidth(theme.fg("muted", scrollHint), w)] : []),
|
|
45
|
+
];
|
|
46
|
+
},
|
|
47
|
+
handleInput(data: string) {
|
|
48
|
+
if (closed) return;
|
|
49
|
+
if (bindings.matches(data, "tui.select.cancel") || lib.matchesKey(data, lib.Key.ctrl("c"))) { finish(false); return; }
|
|
50
|
+
if (bindings.matches(data, "tui.select.confirm")) { finish(true); return; }
|
|
51
|
+
if (lib.matchesKey(data, lib.Key.up)) scroll.scrollBy(-1);
|
|
52
|
+
else if (lib.matchesKey(data, lib.Key.down)) scroll.scrollBy(1);
|
|
53
|
+
else if (lib.matchesKey(data, lib.Key.pageUp)) scroll.scrollBy(-height);
|
|
54
|
+
else if (lib.matchesKey(data, lib.Key.pageDown)) scroll.scrollBy(height);
|
|
55
|
+
else if (lib.matchesKey(data, lib.Key.home)) scroll.scrollToStart();
|
|
56
|
+
else if (lib.matchesKey(data, lib.Key.end)) scroll.scrollToEnd();
|
|
57
|
+
// No input/editor exists: typing and paste cannot alter the displayed text.
|
|
58
|
+
},
|
|
59
|
+
invalidate() { text.invalidate(); scroll.invalidate(); },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function showProblemView(
|
|
64
|
+
ctx: Pick<ExtensionContext, "ui" | "mode">,
|
|
65
|
+
title: string, body: string, action: string, cancel: string,
|
|
66
|
+
): Promise<boolean> {
|
|
67
|
+
if (ctx.mode !== "tui") {
|
|
68
|
+
return await ctx.ui.editor(`${title} — Enter: ${action}; Esc: ${cancel} (not an upload)`,
|
|
69
|
+
`${body}\n\nViewer only. Enter navigates; it does not upload. Edits are discarded.`) !== undefined;
|
|
70
|
+
}
|
|
71
|
+
const lib = await import("@earendil-works/pi-tui");
|
|
72
|
+
return ctx.ui.custom<boolean>((tui, theme, bindings, done) =>
|
|
73
|
+
createProblemView(lib, tui, theme, bindings, done, title, body, action, cancel));
|
|
74
|
+
}
|
package/problems.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { resolveApiOrigin } from "./config.ts";
|
|
3
|
+
import { showProblemView } from "./problem-view.ts";
|
|
3
4
|
|
|
4
5
|
export const MAX_PROBLEM_BODY_BYTES = 64 * 1024;
|
|
5
6
|
const MAX_STRING_CHARACTERS = 1000;
|
|
6
7
|
const UNAVAILABLE = "Problem list unavailable. Check the configured server and connection, then try /golf-problems again.";
|
|
7
|
-
const ADVISORY = "Informational only: browsing does not start or bind an attempt. Upload checks remain authoritative.
|
|
8
|
+
const ADVISORY = "Informational only: browsing does not start or bind an attempt. Upload checks remain authoritative. Nothing is uploaded by browsing.";
|
|
8
9
|
|
|
9
10
|
interface Problem {
|
|
10
11
|
slug: string;
|
|
@@ -72,7 +73,7 @@ async function readCatalogue(response: Response): Promise<Catalogue | undefined>
|
|
|
72
73
|
let browsing = false;
|
|
73
74
|
export async function browseProblems(
|
|
74
75
|
args: string,
|
|
75
|
-
ctx: Pick<ExtensionContext, "ui" | "hasUI">,
|
|
76
|
+
ctx: Pick<ExtensionContext, "ui" | "hasUI" | "mode">,
|
|
76
77
|
options: ProblemOptions = {},
|
|
77
78
|
): Promise<Result> {
|
|
78
79
|
if (args.trim()) return { message: "Usage: /golf-problems", type: "info" };
|
|
@@ -94,22 +95,21 @@ export async function browseProblems(
|
|
|
94
95
|
if (!data) return { message: UNAVAILABLE, type: "error" };
|
|
95
96
|
const rules = [`Rules version: ${data.rules_version}`, "", ...data.rules, "", ADVISORY];
|
|
96
97
|
if (!data.problems.length) rules.push("", "No problems are currently listed.");
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (data.problems.length) {
|
|
98
|
+
const continued = await showProblemView(ctx, "Agent Golf rules", rules.join("\n"),
|
|
99
|
+
data.problems.length ? "view problems" : "close", "close");
|
|
100
|
+
if (continued && data.problems.length) {
|
|
101
101
|
const labels = data.problems.map((p) => `${p.title.replace(/[\n\t]/g, " ")} (${p.slug})`);
|
|
102
102
|
while (true) {
|
|
103
|
-
const selected = await ctx.ui.select("Agent Golf problems —
|
|
103
|
+
const selected = await ctx.ui.select("Agent Golf problems — choose to view details", labels);
|
|
104
104
|
const index = selected === undefined ? -1 : labels.indexOf(selected);
|
|
105
105
|
if (index < 0) break;
|
|
106
106
|
const problem = data.problems[index];
|
|
107
|
-
await ctx
|
|
107
|
+
await showProblemView(ctx, "Problem details", [
|
|
108
108
|
problem.title, `Problem: ${problem.slug}`, problem.challenge_url,
|
|
109
109
|
`Model: ${problem.model === "any" ? "any model, one per session" : problem.model}`,
|
|
110
110
|
`Thinking level: ${problem.thinking_level === "any" ? "any thinking level" : problem.thinking_level}`,
|
|
111
111
|
"", ...problem.instructions, "", ADVISORY,
|
|
112
|
-
].join("\n"));
|
|
112
|
+
].join("\n"), "back to problems", "back to problems");
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
return { message: "Problem browsing closed. No attempt or setup was changed.", type: "info" };
|