glm-coding-router 1.1.2 → 2.1.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.
Files changed (45) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +542 -426
  3. package/dist/bin/glm-review.js +28 -3
  4. package/dist/bin/glm-worker.js +30 -4
  5. package/dist/budget/estimator.js +218 -0
  6. package/dist/budget/manager.js +223 -0
  7. package/dist/cli.js +46 -3
  8. package/dist/commands/dashboard.js +348 -0
  9. package/dist/commands/doctor-auth.js +107 -0
  10. package/dist/commands/doctor-command.js +171 -41
  11. package/dist/commands/landing.js +47 -0
  12. package/dist/commands/runs.js +568 -0
  13. package/dist/commands/status.js +28 -15
  14. package/dist/commands/usage.js +34 -58
  15. package/dist/commands/watch.js +289 -0
  16. package/dist/core/config.js +61 -0
  17. package/dist/core/errors.js +24 -0
  18. package/dist/core/key-inspector.js +45 -0
  19. package/dist/core/paths.js +32 -0
  20. package/dist/core/process.js +83 -0
  21. package/dist/core/prompt.js +18 -5
  22. package/dist/core/routing-flags.js +59 -0
  23. package/dist/core/user-env.js +17 -7
  24. package/dist/core/zai-quota.js +148 -0
  25. package/dist/events/bus.js +64 -0
  26. package/dist/events/claude-adapter.js +416 -0
  27. package/dist/events/types.js +9 -0
  28. package/dist/handoff/bundle.js +203 -0
  29. package/dist/handoff/parent-handoff.js +48 -0
  30. package/dist/mcp/server.js +45 -1
  31. package/dist/routing/glm-routing.js +131 -0
  32. package/dist/runs/checkpoint.js +204 -0
  33. package/dist/runs/drain.js +165 -0
  34. package/dist/runs/heartbeat.js +45 -0
  35. package/dist/runs/registry.js +350 -0
  36. package/dist/runs/store.js +186 -0
  37. package/dist/runs/ulid.js +112 -0
  38. package/dist/runs/worker-run.js +672 -0
  39. package/dist/templates/agents-block.js +53 -44
  40. package/dist/templates/claude-block.js +56 -47
  41. package/dist/templates/glm-delegation-skill.js +76 -65
  42. package/dist/tui/command-ui.js +158 -0
  43. package/dist/tui/progress.js +338 -0
  44. package/dist/tui/render.js +144 -0
  45. package/package.json +1 -1
@@ -1,46 +1,55 @@
1
1
  /** Managed block content for AGENTS.md (spec §24). Keep in sync with the spec. */
2
- export const AGENTS_MANAGED_BLOCK = `<!-- glm-coding-router:start -->
3
-
4
- ## GLM Worker Delegation
5
-
6
- Available commands:
7
-
8
- - \`glm-worker "<task>"\`
9
- - \`glm-review "<task>"\`
10
-
11
- Codex is the primary orchestrator.
12
-
13
- Delegate:
14
- - CRUD
15
- - boilerplate
16
- - tests
17
- - documentation
18
- - mechanical refactoring
19
- - repository exploration
20
- - straightforward implementation
21
-
22
- Keep in Codex:
23
- - requirements
24
- - planning
25
- - architecture
26
- - ambiguous business logic
27
- - complex debugging
28
- - security decisions
29
- - integration
30
- - final review
31
-
32
- Before delegation create a task packet:
33
-
34
- TASK
35
- SCOPE
36
- FILES ALLOWED TO MODIFY
37
- FILES NOT TO MODIFY
38
- REQUIREMENTS
39
- CONSTRAINTS
40
- ACCEPTANCE CRITERIA
41
- VALIDATION
42
- EXPECTED OUTPUT
43
-
44
- Never trust a worker's success report without inspecting the resulting changes.
45
-
2
+ export const AGENTS_MANAGED_BLOCK = `<!-- glm-coding-router:start -->
3
+
4
+ ## GLM Worker Delegation
5
+
6
+ Available commands:
7
+
8
+ - \`glm-worker "<task>"\`
9
+ - \`glm-review "<task>"\`
10
+
11
+ Codex is the primary orchestrator.
12
+
13
+ Delegate:
14
+ - CRUD
15
+ - boilerplate
16
+ - tests
17
+ - documentation
18
+ - mechanical refactoring
19
+ - repository exploration
20
+ - straightforward implementation
21
+
22
+ Keep in Codex:
23
+ - requirements
24
+ - planning
25
+ - architecture
26
+ - ambiguous business logic
27
+ - complex debugging
28
+ - security decisions
29
+ - integration
30
+ - final review
31
+
32
+ Before delegation create a task packet:
33
+
34
+ TASK
35
+ SCOPE
36
+ FILES ALLOWED TO MODIFY
37
+ FILES NOT TO MODIFY
38
+ REQUIREMENTS
39
+ CONSTRAINTS
40
+ ACCEPTANCE CRITERIA
41
+ VALIDATION
42
+ EXPECTED OUTPUT
43
+
44
+ Never trust a worker's success report without inspecting the resulting changes.
45
+
46
+ Worker exit codes 41 and 42 are NOT crashes:
47
+
48
+ - stdout carries one JSON object: \`{"status":"handoff_required", ...}\`
49
+ - read \`handoff_path\` — a handoff.md with what was done, what remains, files
50
+ changed, and untracked files that are NOT in diff.patch
51
+ - continue the task yourself in the SAME worktree, starting from "Remaining"
52
+ - do not re-run the worker until quota resets
53
+ - 41 means nothing was spawned; 42 means work was done and is preserved
54
+
46
55
  <!-- glm-coding-router:end -->`;
@@ -1,49 +1,58 @@
1
1
  /** Managed block content for CLAUDE.md (spec §20). Keep in sync with the spec. */
2
- export const CLAUDE_MANAGED_BLOCK = `<!-- glm-coding-router:start -->
3
-
4
- ## GLM Worker Delegation
5
-
6
- GLM workers available:
7
-
8
- - \`glm-worker "<task>"\`
9
- - \`glm-review "<task>"\`
10
-
11
- Delegate well-scoped, implementation-heavy work to GLM.
12
-
13
- Use GLM for:
14
- - repository exploration
15
- - CRUD
16
- - boilerplate
17
- - tests
18
- - documentation
19
- - mechanical refactoring
20
- - straightforward implementation
21
-
22
- Claude remains responsible for:
23
- - requirements
24
- - architecture
25
- - ambiguous business rules
26
- - security-sensitive decisions
27
- - complex debugging
28
- - integration
29
- - final review
30
-
31
- Before delegation, define:
32
- - task
33
- - scope
34
- - allowed files
35
- - forbidden files
36
- - requirements
37
- - constraints
38
- - acceptance criteria
39
- - validation command
40
- - expected output
41
-
42
- After worker completion:
43
- 1. inspect the actual diff
44
- 2. validate against requirements
45
- 3. run relevant tests
46
- 4. resolve integration problems
47
- 5. accept only after verification
48
-
2
+ export const CLAUDE_MANAGED_BLOCK = `<!-- glm-coding-router:start -->
3
+
4
+ ## GLM Worker Delegation
5
+
6
+ GLM workers available:
7
+
8
+ - \`glm-worker "<task>"\`
9
+ - \`glm-review "<task>"\`
10
+
11
+ Delegate well-scoped, implementation-heavy work to GLM.
12
+
13
+ Use GLM for:
14
+ - repository exploration
15
+ - CRUD
16
+ - boilerplate
17
+ - tests
18
+ - documentation
19
+ - mechanical refactoring
20
+ - straightforward implementation
21
+
22
+ Claude remains responsible for:
23
+ - requirements
24
+ - architecture
25
+ - ambiguous business rules
26
+ - security-sensitive decisions
27
+ - complex debugging
28
+ - integration
29
+ - final review
30
+
31
+ Before delegation, define:
32
+ - task
33
+ - scope
34
+ - allowed files
35
+ - forbidden files
36
+ - requirements
37
+ - constraints
38
+ - acceptance criteria
39
+ - validation command
40
+ - expected output
41
+
42
+ After worker completion:
43
+ 1. inspect the actual diff
44
+ 2. validate against requirements
45
+ 3. run relevant tests
46
+ 4. resolve integration problems
47
+ 5. accept only after verification
48
+
49
+ Worker exit codes 41 and 42 are NOT crashes:
50
+
51
+ - stdout carries one JSON object: \`{"status":"handoff_required", ...}\`
52
+ - read \`handoff_path\` — a handoff.md with what was done, what remains, files
53
+ changed, and untracked files that are NOT in diff.patch
54
+ - continue the task yourself in the SAME worktree, starting from "Remaining"
55
+ - do not re-run the worker until quota resets
56
+ - 41 means nothing was spawned; 42 means work was done and is preserved
57
+
49
58
  <!-- glm-coding-router:end -->`;
@@ -1,68 +1,79 @@
1
1
  /** Codex skill definition (spec §26). Keep in sync with the spec. */
2
2
  export const GLM_DELEGATION_SKILL_NAME = "glm-delegation";
3
- export const GLM_DELEGATION_SKILL_MD = `---
4
- name: glm-delegation
5
- description: >
6
- Delegate well-scoped implementation, testing,
7
- repository exploration, boilerplate, CRUD,
8
- documentation, and mechanical refactoring to
9
- GLM Coding Plan workers.
10
- ---
11
-
12
- # GLM Delegation
13
-
14
- Available commands:
15
-
16
- glm-worker "<task>"
17
- glm-review "<task>"
18
-
19
- ## Use glm-review for
20
-
21
- - repository exploration
22
- - dependency analysis
23
- - locating implementations
24
- - call-chain discovery
25
- - code review
26
-
27
- ## Use glm-worker for
28
-
29
- - CRUD
30
- - unit tests
31
- - implementation
32
- - documentation
33
- - repetitive changes
34
- - mechanical refactoring
35
-
36
- ## Keep in primary Codex agent
37
-
38
- - requirements
39
- - architecture
40
- - ambiguous rules
41
- - security-sensitive design
42
- - difficult debugging
43
- - integration
44
- - final acceptance
45
-
46
- ## Delegation packet
47
-
48
- Always provide:
49
-
50
- TASK
51
- SCOPE
52
- ALLOWED FILES
53
- FORBIDDEN FILES
54
- REQUIREMENTS
55
- CONSTRAINTS
56
- ACCEPTANCE CRITERIA
57
- VALIDATION
58
- EXPECTED OUTPUT
59
-
60
- ## Verification
61
-
62
- After GLM finishes:
63
-
64
- - inspect the actual diff
65
- - independently run relevant validation
66
- - compare implementation with requirements
67
- - reject or correct worker output when needed
3
+ export const GLM_DELEGATION_SKILL_MD = `---
4
+ name: glm-delegation
5
+ description: >
6
+ Delegate well-scoped implementation, testing,
7
+ repository exploration, boilerplate, CRUD,
8
+ documentation, and mechanical refactoring to
9
+ GLM Coding Plan workers.
10
+ ---
11
+
12
+ # GLM Delegation
13
+
14
+ Available commands:
15
+
16
+ glm-worker "<task>"
17
+ glm-review "<task>"
18
+
19
+ ## Use glm-review for
20
+
21
+ - repository exploration
22
+ - dependency analysis
23
+ - locating implementations
24
+ - call-chain discovery
25
+ - code review
26
+
27
+ ## Use glm-worker for
28
+
29
+ - CRUD
30
+ - unit tests
31
+ - implementation
32
+ - documentation
33
+ - repetitive changes
34
+ - mechanical refactoring
35
+
36
+ ## Keep in primary Codex agent
37
+
38
+ - requirements
39
+ - architecture
40
+ - ambiguous rules
41
+ - security-sensitive design
42
+ - difficult debugging
43
+ - integration
44
+ - final acceptance
45
+
46
+ ## Delegation packet
47
+
48
+ Always provide:
49
+
50
+ TASK
51
+ SCOPE
52
+ ALLOWED FILES
53
+ FORBIDDEN FILES
54
+ REQUIREMENTS
55
+ CONSTRAINTS
56
+ ACCEPTANCE CRITERIA
57
+ VALIDATION
58
+ EXPECTED OUTPUT
59
+
60
+ ## Verification
61
+
62
+ After GLM finishes:
63
+
64
+ - inspect the actual diff
65
+ - independently run relevant validation
66
+ - compare implementation with requirements
67
+ - reject or correct worker output when needed
68
+
69
+ ## Exit codes 41 and 42 are not crashes
70
+
71
+ A worker may stop because the GLM quota ran low rather than because it broke.
72
+
73
+ - stdout carries one JSON object: \`{"status":"handoff_required", ...}\`
74
+ - read \`handoff_path\` — a handoff.md with what was done, what remains, files
75
+ changed, and untracked files that are NOT in diff.patch
76
+ - continue the task yourself in the SAME worktree, starting from "Remaining"
77
+ - do not re-run the worker until quota resets
78
+ - 41 means nothing was spawned; 42 means work was done and is preserved
68
79
  `;
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Shared presentation layer for the management screens — `doctor`, `status`,
3
+ * `usage`, and the root landing page (specs/terminal-ui-doctor.md §A). This
4
+ * module never fetches data, reads credentials, or makes routing decisions;
5
+ * it only turns already-computed values into aligned, width-aware text.
6
+ *
7
+ * All ANSI stays in render.ts (`paint`); this file composes plain strings
8
+ * and colors them through that one choke point.
9
+ */
10
+ import { displayWidth, padEndDisplay, paint, truncate } from "./render.js";
11
+ const STATUS_TAG = {
12
+ ok: "[OK]",
13
+ warn: "[WARN]",
14
+ fail: "[FAIL]",
15
+ info: "[INFO]",
16
+ };
17
+ const STATUS_STYLE = {
18
+ ok: "green",
19
+ warn: "yellow",
20
+ fail: "red",
21
+ info: "cyan",
22
+ };
23
+ /** Widest tag ("[WARN]"/"[FAIL]"/"[INFO]") plus one separating space. */
24
+ const TAG_COLUMN = 7;
25
+ const BASE_INDENT = " ";
26
+ const LABEL_COLUMN = 24;
27
+ const MIN_BOX_WIDTH = 44;
28
+ const MAX_BOX_WIDTH = 78;
29
+ const BAR_SLOTS = 20;
30
+ function effectiveWidth(writer) {
31
+ return Number.isFinite(writer.columns) && writer.columns > 0 ? Math.floor(writer.columns) : 80;
32
+ }
33
+ /** Strip control characters from text that did not originate in this module (spec §A). */
34
+ function sanitize(text, allowNewline) {
35
+ const pattern = allowNewline ? /[\x00-\x09\x0B\x0C\x0E-\x1F\x7F]/g : /[\x00-\x1F\x7F]/g;
36
+ return text.replace(pattern, "");
37
+ }
38
+ function border(width) {
39
+ return `+${"-".repeat(Math.max(width - 2, 0))}+`;
40
+ }
41
+ function boxLine(text, width) {
42
+ const inner = Math.max(width - 4, 0);
43
+ return `| ${padEndDisplay(truncate(text, inner), inner)} |`;
44
+ }
45
+ /**
46
+ * Wrap `text` to `width`-wide lines (measured in display columns, not code
47
+ * units — spec §A "Width uses display cells") without breaking a word
48
+ * mid-way when avoidable.
49
+ */
50
+ function wrap(text, width) {
51
+ if (width <= 0)
52
+ return [text];
53
+ const words = text.split(/\s+/).filter((w) => w.length > 0);
54
+ const lines = [];
55
+ let current = "";
56
+ for (const word of words) {
57
+ const candidate = current.length === 0 ? word : `${current} ${word}`;
58
+ if (displayWidth(candidate) > width && current.length > 0) {
59
+ lines.push(current);
60
+ current = word;
61
+ }
62
+ else {
63
+ current = candidate;
64
+ }
65
+ }
66
+ if (current.length > 0)
67
+ lines.push(current);
68
+ return lines.length > 0 ? lines : [""];
69
+ }
70
+ export function createCommandUi(writer, options = {}) {
71
+ const quiet = options.quiet ?? false;
72
+ return {
73
+ header(title, subtitle) {
74
+ if (quiet)
75
+ return "";
76
+ const width = effectiveWidth(writer);
77
+ const cleanTitle = sanitize(title, false);
78
+ const cleanSubtitle = subtitle !== undefined ? sanitize(subtitle, false) : undefined;
79
+ if (width < MIN_BOX_WIDTH) {
80
+ const lines = [paint(writer, "cyan", truncate(cleanTitle, width))];
81
+ if (cleanSubtitle)
82
+ lines.push(paint(writer, "dim", truncate(cleanSubtitle, width)));
83
+ return lines.join("\n");
84
+ }
85
+ const boxWidth = Math.min(width, MAX_BOX_WIDTH);
86
+ const lines = [border(boxWidth), boxLine(cleanTitle, boxWidth)];
87
+ if (cleanSubtitle)
88
+ lines.push(boxLine(cleanSubtitle, boxWidth));
89
+ lines.push(border(boxWidth));
90
+ return lines.join("\n");
91
+ },
92
+ section(title) {
93
+ return paint(writer, "bold", sanitize(title, false));
94
+ },
95
+ row(label, value, status) {
96
+ const width = effectiveWidth(writer);
97
+ const cleanLabel = sanitize(label, false);
98
+ const cleanValue = sanitize(value, false);
99
+ const tagField = status !== undefined ? STATUS_TAG[status].padEnd(TAG_COLUMN) : "";
100
+ const labelField = padEndDisplay(cleanLabel, LABEL_COLUMN);
101
+ const plainPrefix = `${BASE_INDENT}${tagField}${labelField}`;
102
+ const singleLine = `${plainPrefix}${cleanValue}`;
103
+ const coloredPrefix = () => {
104
+ if (status === undefined)
105
+ return plainPrefix;
106
+ const tag = STATUS_TAG[status];
107
+ return `${BASE_INDENT}${paint(writer, STATUS_STYLE[status], tag)}${tagField.slice(tag.length)}${labelField}`;
108
+ };
109
+ if (cleanValue.length === 0 || displayWidth(singleLine.replace(/\s+$/, "")) <= width) {
110
+ return `${coloredPrefix()}${cleanValue}`.replace(/\s+$/, "");
111
+ }
112
+ // Stack: tag + label on one line, value wrapped and indented beneath.
113
+ const tagLine = status !== undefined
114
+ ? `${BASE_INDENT}${paint(writer, STATUS_STYLE[status], STATUS_TAG[status])} ${cleanLabel}`
115
+ : `${BASE_INDENT}${cleanLabel}`;
116
+ const valueIndent = `${BASE_INDENT}${" ".repeat(TAG_COLUMN)}`;
117
+ const valueWidth = Math.max(width - valueIndent.length, 8);
118
+ const valueLines = wrap(cleanValue, valueWidth).map((line) => `${valueIndent}${line}`);
119
+ return [tagLine, ...valueLines].join("\n");
120
+ },
121
+ detail(text) {
122
+ const width = effectiveWidth(writer);
123
+ const indent = `${BASE_INDENT}${" ".repeat(TAG_COLUMN - 2)}`;
124
+ const availableWidth = Math.max(width - indent.length, 8);
125
+ const cleanText = sanitize(text, true);
126
+ const lines = [];
127
+ for (const paragraph of cleanText.split("\n")) {
128
+ if (paragraph.length === 0) {
129
+ lines.push("");
130
+ continue;
131
+ }
132
+ for (const line of wrap(paragraph, availableWidth)) {
133
+ lines.push(`${indent}${line}`);
134
+ }
135
+ }
136
+ return lines.join("\n");
137
+ },
138
+ footer(text) {
139
+ if (quiet)
140
+ return "";
141
+ const cleanText = sanitize(text, true);
142
+ return cleanText
143
+ .split("\n")
144
+ .map((line) => (line.length === 0 ? "" : `${BASE_INDENT}${line}`))
145
+ .join("\n");
146
+ },
147
+ bar(percent) {
148
+ if (percent === undefined || !Number.isFinite(percent)) {
149
+ return `[${"-".repeat(BAR_SLOTS)}] unknown`;
150
+ }
151
+ const clamped = Math.min(100, Math.max(0, percent));
152
+ const filled = Math.round((clamped / 100) * BAR_SLOTS);
153
+ const bar = `[${"#".repeat(filled)}${"-".repeat(BAR_SLOTS - filled)}]`;
154
+ const style = clamped >= 90 ? "red" : clamped >= 70 ? "yellow" : "green";
155
+ return `${paint(writer, style, bar)} ${Math.round(clamped)}% used`;
156
+ },
157
+ };
158
+ }