@joshski/dust 0.1.44 → 0.1.46
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/dist/agent-events.d.ts +0 -4
- package/dist/cli/types.d.ts +1 -0
- package/dist/dust.js +427 -195
- package/package.json +1 -1
package/dist/agent-events.d.ts
CHANGED
|
@@ -39,8 +39,4 @@ export interface EventMessage {
|
|
|
39
39
|
* is forwarded as a claude-event.
|
|
40
40
|
*/
|
|
41
41
|
export declare function rawEventToAgentEvent(rawEvent: Record<string, unknown>): AgentSessionEvent;
|
|
42
|
-
/**
|
|
43
|
-
* Format an AgentSessionEvent for console output.
|
|
44
|
-
* Returns null for events that should not be displayed.
|
|
45
|
-
*/
|
|
46
42
|
export declare function formatAgentEvent(event: AgentSessionEvent): string | null;
|
package/dist/cli/types.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export interface FileSystem {
|
|
|
22
22
|
readdir: (path: string) => Promise<string[]>;
|
|
23
23
|
chmod: (path: string, mode: number) => Promise<void>;
|
|
24
24
|
isDirectory: (path: string) => boolean;
|
|
25
|
+
getFileCreationTime: (path: string) => number;
|
|
25
26
|
}
|
|
26
27
|
export interface GlobScanner {
|
|
27
28
|
scan: (dir: string) => AsyncIterable<string>;
|
package/dist/dust.js
CHANGED
|
@@ -18,129 +18,128 @@ var KNOWN_CHECK_KEYS = new Set([
|
|
|
18
18
|
"hints",
|
|
19
19
|
"timeoutMilliseconds"
|
|
20
20
|
]);
|
|
21
|
-
function
|
|
21
|
+
function validateCheckEntry(check, checkPath) {
|
|
22
22
|
const violations = [];
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
parsed = JSON.parse(content);
|
|
26
|
-
} catch (error) {
|
|
27
|
-
violations.push({
|
|
28
|
-
message: `Invalid JSON: ${error.message}`
|
|
29
|
-
});
|
|
23
|
+
if (typeof check === "string") {
|
|
30
24
|
return violations;
|
|
31
25
|
}
|
|
32
|
-
if (typeof
|
|
33
|
-
violations.push({
|
|
34
|
-
message: "settings.json must be a JSON object"
|
|
35
|
-
});
|
|
26
|
+
if (typeof check !== "object" || check === null || Array.isArray(check)) {
|
|
27
|
+
violations.push({ message: `${checkPath} must be a string or object` });
|
|
36
28
|
return violations;
|
|
37
29
|
}
|
|
38
|
-
const
|
|
39
|
-
for (const key of Object.keys(
|
|
40
|
-
if (!
|
|
30
|
+
const checkObj = check;
|
|
31
|
+
for (const key of Object.keys(checkObj)) {
|
|
32
|
+
if (!KNOWN_CHECK_KEYS.has(key)) {
|
|
41
33
|
violations.push({
|
|
42
|
-
message: `Unknown key "${key}" in
|
|
34
|
+
message: `Unknown key "${key}" in ${checkPath}. Known keys: ${[...KNOWN_CHECK_KEYS].sort().join(", ")}`
|
|
43
35
|
});
|
|
44
36
|
}
|
|
45
37
|
}
|
|
46
|
-
if ("
|
|
47
|
-
|
|
38
|
+
if (!("name" in checkObj)) {
|
|
39
|
+
violations.push({
|
|
40
|
+
message: `${checkPath} is missing required field "name"`
|
|
41
|
+
});
|
|
42
|
+
} else if (typeof checkObj.name !== "string") {
|
|
43
|
+
violations.push({ message: `${checkPath}.name must be a string` });
|
|
44
|
+
}
|
|
45
|
+
if (!("command" in checkObj)) {
|
|
46
|
+
violations.push({
|
|
47
|
+
message: `${checkPath} is missing required field "command"`
|
|
48
|
+
});
|
|
49
|
+
} else if (typeof checkObj.command !== "string") {
|
|
50
|
+
violations.push({ message: `${checkPath}.command must be a string` });
|
|
51
|
+
}
|
|
52
|
+
if ("hints" in checkObj) {
|
|
53
|
+
if (!Array.isArray(checkObj.hints)) {
|
|
48
54
|
violations.push({
|
|
49
|
-
message:
|
|
55
|
+
message: `${checkPath}.hints must be an array of strings`
|
|
50
56
|
});
|
|
51
57
|
} else {
|
|
52
|
-
for (let
|
|
53
|
-
|
|
54
|
-
const checkPath = `checks[${i}]`;
|
|
55
|
-
if (typeof check === "string") {
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
if (typeof check !== "object" || check === null || Array.isArray(check)) {
|
|
59
|
-
violations.push({
|
|
60
|
-
message: `${checkPath} must be a string or object`
|
|
61
|
-
});
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
const checkObj = check;
|
|
65
|
-
for (const key of Object.keys(checkObj)) {
|
|
66
|
-
if (!KNOWN_CHECK_KEYS.has(key)) {
|
|
67
|
-
violations.push({
|
|
68
|
-
message: `Unknown key "${key}" in ${checkPath}. Known keys: ${[...KNOWN_CHECK_KEYS].sort().join(", ")}`
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
if (!("name" in checkObj)) {
|
|
73
|
-
violations.push({
|
|
74
|
-
message: `${checkPath} is missing required field "name"`
|
|
75
|
-
});
|
|
76
|
-
} else if (typeof checkObj.name !== "string") {
|
|
77
|
-
violations.push({
|
|
78
|
-
message: `${checkPath}.name must be a string`
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
if (!("command" in checkObj)) {
|
|
82
|
-
violations.push({
|
|
83
|
-
message: `${checkPath} is missing required field "command"`
|
|
84
|
-
});
|
|
85
|
-
} else if (typeof checkObj.command !== "string") {
|
|
58
|
+
for (let j = 0;j < checkObj.hints.length; j++) {
|
|
59
|
+
if (typeof checkObj.hints[j] !== "string") {
|
|
86
60
|
violations.push({
|
|
87
|
-
message: `${checkPath}.
|
|
61
|
+
message: `${checkPath}.hints[${j}] must be a string`
|
|
88
62
|
});
|
|
89
63
|
}
|
|
90
|
-
if ("hints" in checkObj) {
|
|
91
|
-
if (!Array.isArray(checkObj.hints)) {
|
|
92
|
-
violations.push({
|
|
93
|
-
message: `${checkPath}.hints must be an array of strings`
|
|
94
|
-
});
|
|
95
|
-
} else {
|
|
96
|
-
for (let j = 0;j < checkObj.hints.length; j++) {
|
|
97
|
-
if (typeof checkObj.hints[j] !== "string") {
|
|
98
|
-
violations.push({
|
|
99
|
-
message: `${checkPath}.hints[${j}] must be a string`
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
if ("timeoutMilliseconds" in checkObj) {
|
|
106
|
-
if (typeof checkObj.timeoutMilliseconds !== "number" || checkObj.timeoutMilliseconds <= 0) {
|
|
107
|
-
violations.push({
|
|
108
|
-
message: `${checkPath}.timeoutMilliseconds must be a positive number`
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
64
|
}
|
|
113
65
|
}
|
|
114
66
|
}
|
|
115
|
-
if ("
|
|
116
|
-
if (
|
|
67
|
+
if ("timeoutMilliseconds" in checkObj) {
|
|
68
|
+
if (typeof checkObj.timeoutMilliseconds !== "number" || checkObj.timeoutMilliseconds <= 0) {
|
|
117
69
|
violations.push({
|
|
118
|
-
message:
|
|
70
|
+
message: `${checkPath}.timeoutMilliseconds must be a positive number`
|
|
119
71
|
});
|
|
120
|
-
} else {
|
|
121
|
-
for (let i = 0;i < settings.extraDirectories.length; i++) {
|
|
122
|
-
if (typeof settings.extraDirectories[i] !== "string") {
|
|
123
|
-
violations.push({
|
|
124
|
-
message: `extraDirectories[${i}] must be a string`
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
72
|
}
|
|
129
73
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
74
|
+
return violations;
|
|
75
|
+
}
|
|
76
|
+
function validateChecksConfig(settings) {
|
|
77
|
+
if (!("checks" in settings)) {
|
|
78
|
+
return [];
|
|
134
79
|
}
|
|
135
|
-
if (
|
|
80
|
+
if (!Array.isArray(settings.checks)) {
|
|
81
|
+
return [{ message: '"checks" must be an array' }];
|
|
82
|
+
}
|
|
83
|
+
const violations = [];
|
|
84
|
+
for (let i = 0;i < settings.checks.length; i++) {
|
|
85
|
+
violations.push(...validateCheckEntry(settings.checks[i], `checks[${i}]`));
|
|
86
|
+
}
|
|
87
|
+
return violations;
|
|
88
|
+
}
|
|
89
|
+
function validateExtraDirectories(settings) {
|
|
90
|
+
if (!("extraDirectories" in settings)) {
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
if (!Array.isArray(settings.extraDirectories)) {
|
|
94
|
+
return [{ message: '"extraDirectories" must be an array of strings' }];
|
|
95
|
+
}
|
|
96
|
+
const violations = [];
|
|
97
|
+
for (let i = 0;i < settings.extraDirectories.length; i++) {
|
|
98
|
+
if (typeof settings.extraDirectories[i] !== "string") {
|
|
99
|
+
violations.push({ message: `extraDirectories[${i}] must be a string` });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return violations;
|
|
103
|
+
}
|
|
104
|
+
function validateDustEventsUrl(settings) {
|
|
105
|
+
if ("eventsUrl" in settings && typeof settings.eventsUrl !== "string") {
|
|
106
|
+
return [{ message: '"eventsUrl" must be a string' }];
|
|
107
|
+
}
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
function validateSettingsJson(content) {
|
|
111
|
+
const violations = [];
|
|
112
|
+
let parsed;
|
|
113
|
+
try {
|
|
114
|
+
parsed = JSON.parse(content);
|
|
115
|
+
} catch (error) {
|
|
136
116
|
violations.push({
|
|
137
|
-
message:
|
|
117
|
+
message: `Invalid JSON: ${error.message}`
|
|
138
118
|
});
|
|
119
|
+
return violations;
|
|
139
120
|
}
|
|
140
|
-
if ("
|
|
121
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
141
122
|
violations.push({
|
|
142
|
-
message:
|
|
123
|
+
message: "settings.json must be a JSON object"
|
|
143
124
|
});
|
|
125
|
+
return violations;
|
|
126
|
+
}
|
|
127
|
+
const settings = parsed;
|
|
128
|
+
for (const key of Object.keys(settings)) {
|
|
129
|
+
if (!KNOWN_SETTINGS_KEYS.has(key)) {
|
|
130
|
+
violations.push({
|
|
131
|
+
message: `Unknown key "${key}" in settings.json. Known keys: ${[...KNOWN_SETTINGS_KEYS].sort().join(", ")}`
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
violations.push(...validateChecksConfig(settings));
|
|
136
|
+
violations.push(...validateExtraDirectories(settings));
|
|
137
|
+
violations.push(...validateDustEventsUrl(settings));
|
|
138
|
+
if ("dustCommand" in settings && typeof settings.dustCommand !== "string") {
|
|
139
|
+
violations.push({ message: '"dustCommand" must be a string' });
|
|
140
|
+
}
|
|
141
|
+
if ("installCommand" in settings && typeof settings.installCommand !== "string") {
|
|
142
|
+
violations.push({ message: '"installCommand" must be a string' });
|
|
144
143
|
}
|
|
145
144
|
return violations;
|
|
146
145
|
}
|
|
@@ -578,6 +577,10 @@ function agentDeveloperExperience() {
|
|
|
578
577
|
4. **Debugging tools** - Can agents diagnose issues without trial and error?
|
|
579
578
|
5. **Structured logging** - Is system behavior observable through logs?
|
|
580
579
|
|
|
580
|
+
## Goals
|
|
581
|
+
|
|
582
|
+
(none)
|
|
583
|
+
|
|
581
584
|
## Blocked By
|
|
582
585
|
|
|
583
586
|
(none)
|
|
@@ -607,6 +610,10 @@ function deadCode() {
|
|
|
607
610
|
4. **Unused dependencies** - Packages in package.json not used in code
|
|
608
611
|
5. **Commented-out code** - Old code left in comments
|
|
609
612
|
|
|
613
|
+
## Goals
|
|
614
|
+
|
|
615
|
+
(none)
|
|
616
|
+
|
|
610
617
|
## Blocked By
|
|
611
618
|
|
|
612
619
|
(none)
|
|
@@ -637,6 +644,10 @@ function factsVerification() {
|
|
|
637
644
|
3. **Staleness** - Have facts become outdated due to recent changes?
|
|
638
645
|
4. **Relevance** - Are all facts still useful for understanding the project?
|
|
639
646
|
|
|
647
|
+
## Goals
|
|
648
|
+
|
|
649
|
+
(none)
|
|
650
|
+
|
|
640
651
|
## Blocked By
|
|
641
652
|
|
|
642
653
|
(none)
|
|
@@ -666,6 +677,10 @@ function ideasFromCommits() {
|
|
|
666
677
|
3. **Pattern opportunities** - Can recent changes be generalized?
|
|
667
678
|
4. **Test gaps** - Do recent changes have adequate test coverage?
|
|
668
679
|
|
|
680
|
+
## Goals
|
|
681
|
+
|
|
682
|
+
(none)
|
|
683
|
+
|
|
669
684
|
## Blocked By
|
|
670
685
|
|
|
671
686
|
(none)
|
|
@@ -694,6 +709,10 @@ function ideasFromGoals() {
|
|
|
694
709
|
3. **New opportunities** - What work would better achieve each goal?
|
|
695
710
|
4. **Goal alignment** - Are current tasks aligned with stated goals?
|
|
696
711
|
|
|
712
|
+
## Goals
|
|
713
|
+
|
|
714
|
+
(none)
|
|
715
|
+
|
|
697
716
|
## Blocked By
|
|
698
717
|
|
|
699
718
|
(none)
|
|
@@ -722,6 +741,10 @@ function performanceReview() {
|
|
|
722
741
|
4. **Build performance** - How fast is the build process?
|
|
723
742
|
5. **Test speed** - Are tests running efficiently?
|
|
724
743
|
|
|
744
|
+
## Goals
|
|
745
|
+
|
|
746
|
+
(none)
|
|
747
|
+
|
|
725
748
|
## Blocked By
|
|
726
749
|
|
|
727
750
|
(none)
|
|
@@ -751,6 +774,10 @@ function securityReview() {
|
|
|
751
774
|
4. **Sensitive data exposure** - Logging sensitive data, insecure storage
|
|
752
775
|
5. **Dependency vulnerabilities** - Known CVEs in dependencies
|
|
753
776
|
|
|
777
|
+
## Goals
|
|
778
|
+
|
|
779
|
+
(none)
|
|
780
|
+
|
|
754
781
|
## Blocked By
|
|
755
782
|
|
|
756
783
|
(none)
|
|
@@ -781,6 +808,10 @@ function staleIdeas() {
|
|
|
781
808
|
3. **Actionability** - Can the idea be converted to a task?
|
|
782
809
|
4. **Duplication** - Are there overlapping or redundant ideas?
|
|
783
810
|
|
|
811
|
+
## Goals
|
|
812
|
+
|
|
813
|
+
(none)
|
|
814
|
+
|
|
784
815
|
## Blocked By
|
|
785
816
|
|
|
786
817
|
(none)
|
|
@@ -810,6 +841,10 @@ function testCoverage() {
|
|
|
810
841
|
4. **User-facing features** - UI components, form validation
|
|
811
842
|
5. **Recent changes** - Code modified in the last few commits
|
|
812
843
|
|
|
844
|
+
## Goals
|
|
845
|
+
|
|
846
|
+
(none)
|
|
847
|
+
|
|
813
848
|
## Blocked By
|
|
814
849
|
|
|
815
850
|
(none)
|
|
@@ -973,7 +1008,7 @@ import { accessSync, statSync } from "node:fs";
|
|
|
973
1008
|
import { chmod, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
974
1009
|
import { createServer as httpCreateServer } from "node:http";
|
|
975
1010
|
import { homedir } from "node:os";
|
|
976
|
-
import { join as
|
|
1011
|
+
import { join as join8 } from "node:path";
|
|
977
1012
|
|
|
978
1013
|
// lib/bucket/auth.ts
|
|
979
1014
|
import { join as join4 } from "node:path";
|
|
@@ -1046,7 +1081,29 @@ async function authenticate(authDeps) {
|
|
|
1046
1081
|
if (code) {
|
|
1047
1082
|
cleanup();
|
|
1048
1083
|
exchange(code).then(resolve, reject);
|
|
1049
|
-
return new Response(
|
|
1084
|
+
return new Response(`<!DOCTYPE html>
|
|
1085
|
+
<html>
|
|
1086
|
+
<head>
|
|
1087
|
+
<meta charset="utf-8">
|
|
1088
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1089
|
+
<title>Authorized</title>
|
|
1090
|
+
<style>
|
|
1091
|
+
body { font-family: system-ui, sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; background: #0a0a0a; color: #fafafa; }
|
|
1092
|
+
.card { text-align: center; padding: 2rem; border: 1px solid #333; border-radius: 8px; max-width: 400px; }
|
|
1093
|
+
h1 { font-size: 1.25rem; margin-bottom: 1rem; }
|
|
1094
|
+
p { color: #999; }
|
|
1095
|
+
</style>
|
|
1096
|
+
</head>
|
|
1097
|
+
<body>
|
|
1098
|
+
<div class="card">
|
|
1099
|
+
<svg width="64" height="64" viewBox="0 0 512 512" fill="#fafafa" xmlns="http://www.w3.org/2000/svg" style="margin-bottom: 1rem;">
|
|
1100
|
+
<path d="M392.566 159.4c-.649-79.601-31.851-134.481-80.25-141.944l-1.443-10.127a8.52 8.52 0 0 0-3.339-5.619 8.52 8.52 0 0 0-6.327-1.622l-92.99 13.287c-4.671.666-7.916 4.995-7.25 9.666l1.605 11.229c-.709.179-1.417.307-2.075.692-52.122 30.126-68.688 71.677-77.346 122.859-34.293 13.773-55.008 33.157-55.008 55.316 0 11.997 6.242 23.149 17.24 33.072-.12.922 42.712 207.763 42.712 207.763.06.273.128.555.213.828 10.64 33.678 63.146 57.203 127.693 57.203 66.963 0 118.308-23.107 127.906-58.031 0 0 42.832-206.841 42.712-207.763 10.998-9.922 17.24-21.074 17.24-33.072.001-21.34-19.264-40.083-51.293-53.737m-276.281 51.072a2180 2180 0 0 1-4.022 33.712c-17.326-9.606-27.043-20.545-27.043-31.048 0-11.989 12.723-24.541 34.933-35.044-1.374 10.452-2.612 21.219-3.868 32.38m88.849-158.254.658 4.637a8.52 8.52 0 0 0 3.339 5.619 8.5 8.5 0 0 0 5.123 1.708c.401 0 .811-.026 1.213-.085l92.99-13.287c4.671-.666 7.916-4.995 7.25-9.666l-.837-5.858c35.497 9.077 58.509 53.642 60.482 117.634-32.021-10.477-73.214-16.634-119.35-16.634-43.797 0-83.051 5.593-114.312 15.123 8.205-41.8 23.439-74.573 63.444-99.191m50.867 220.691c-52.891 0-97.217-8.735-127.505-21.1 1.819-13.645 3.339-26.778 4.756-39.425 1.648-14.704 3.228-28.555 5.149-41.653 29.724-10.341 70.208-17.368 117.6-17.368 100.641 0 170.781 31.5 170.781 59.773s-70.14 59.773-170.781 59.773"/>
|
|
1101
|
+
</svg>
|
|
1102
|
+
<h1>Your agent is connected!</h1>
|
|
1103
|
+
<p>You can close this tab.</p>
|
|
1104
|
+
</div>
|
|
1105
|
+
</body>
|
|
1106
|
+
</html>`, { headers: { "Content-Type": "text/html" } });
|
|
1050
1107
|
}
|
|
1051
1108
|
return new Response("Missing code", { status: 400 });
|
|
1052
1109
|
}
|
|
@@ -1120,26 +1177,7 @@ function getLogLines(buffer) {
|
|
|
1120
1177
|
}
|
|
1121
1178
|
|
|
1122
1179
|
// lib/bucket/repository.ts
|
|
1123
|
-
import { dirname as dirname2, join as
|
|
1124
|
-
|
|
1125
|
-
// lib/agent-events.ts
|
|
1126
|
-
function rawEventToAgentEvent(rawEvent) {
|
|
1127
|
-
if (typeof rawEvent.type === "string" && rawEvent.type === "stream_event") {
|
|
1128
|
-
return { type: "agent-session-activity" };
|
|
1129
|
-
}
|
|
1130
|
-
return { type: "claude-event", rawEvent };
|
|
1131
|
-
}
|
|
1132
|
-
function formatAgentEvent(event) {
|
|
1133
|
-
switch (event.type) {
|
|
1134
|
-
case "agent-session-started":
|
|
1135
|
-
return `\uD83E\uDD16 Starting Claude: ${event.title}`;
|
|
1136
|
-
case "agent-session-ended":
|
|
1137
|
-
return event.success ? "\uD83E\uDD16 Claude session ended (success)" : `\uD83E\uDD16 Claude session ended (error: ${event.error})`;
|
|
1138
|
-
case "agent-session-activity":
|
|
1139
|
-
case "claude-event":
|
|
1140
|
-
return null;
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1180
|
+
import { dirname as dirname2, join as join7 } from "node:path";
|
|
1143
1181
|
|
|
1144
1182
|
// lib/claude/spawn-claude-code.ts
|
|
1145
1183
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
@@ -1540,11 +1578,81 @@ async function run(prompt, options = {}, dependencies = defaultRunnerDependencie
|
|
|
1540
1578
|
await dependencies.streamEvents(events, sink, onRawEvent);
|
|
1541
1579
|
}
|
|
1542
1580
|
|
|
1581
|
+
// lib/bucket/repository-git.ts
|
|
1582
|
+
import { join as join5 } from "node:path";
|
|
1583
|
+
function getRepoPath(repoName, reposDir) {
|
|
1584
|
+
const safeName = repoName.replace(/[^a-zA-Z0-9-_/]/g, "-");
|
|
1585
|
+
return join5(reposDir, safeName);
|
|
1586
|
+
}
|
|
1587
|
+
async function cloneRepository(repository, targetPath, spawn, context) {
|
|
1588
|
+
return new Promise((resolve) => {
|
|
1589
|
+
const proc = spawn("git", ["clone", repository.gitUrl, targetPath], {
|
|
1590
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1591
|
+
});
|
|
1592
|
+
let stderr = "";
|
|
1593
|
+
proc.stderr?.on("data", (data) => {
|
|
1594
|
+
stderr += data.toString();
|
|
1595
|
+
});
|
|
1596
|
+
proc.on("close", (code) => {
|
|
1597
|
+
if (code === 0) {
|
|
1598
|
+
resolve(true);
|
|
1599
|
+
} else {
|
|
1600
|
+
context.stderr(`Failed to clone ${repository.name}: ${stderr.trim()}`);
|
|
1601
|
+
resolve(false);
|
|
1602
|
+
}
|
|
1603
|
+
});
|
|
1604
|
+
proc.on("error", (error) => {
|
|
1605
|
+
context.stderr(`Failed to clone ${repository.name}: ${error.message}`);
|
|
1606
|
+
resolve(false);
|
|
1607
|
+
});
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
async function removeRepository(path, spawn, context) {
|
|
1611
|
+
return new Promise((resolve) => {
|
|
1612
|
+
const proc = spawn("rm", ["-rf", path], {
|
|
1613
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1614
|
+
});
|
|
1615
|
+
proc.on("close", (code) => {
|
|
1616
|
+
resolve(code === 0);
|
|
1617
|
+
});
|
|
1618
|
+
proc.on("error", (error) => {
|
|
1619
|
+
context.stderr(`Failed to remove ${path}: ${error.message}`);
|
|
1620
|
+
resolve(false);
|
|
1621
|
+
});
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
// lib/agent-events.ts
|
|
1626
|
+
function rawEventToAgentEvent(rawEvent) {
|
|
1627
|
+
if (typeof rawEvent.type === "string" && rawEvent.type === "stream_event") {
|
|
1628
|
+
return { type: "agent-session-activity" };
|
|
1629
|
+
}
|
|
1630
|
+
return { type: "claude-event", rawEvent };
|
|
1631
|
+
}
|
|
1632
|
+
function agentDisplayName(agentType) {
|
|
1633
|
+
if (agentType === "codex")
|
|
1634
|
+
return "Codex";
|
|
1635
|
+
return "Claude";
|
|
1636
|
+
}
|
|
1637
|
+
function formatAgentEvent(event) {
|
|
1638
|
+
switch (event.type) {
|
|
1639
|
+
case "agent-session-started": {
|
|
1640
|
+
const name = agentDisplayName(event.agentType);
|
|
1641
|
+
return `\uD83E\uDD16 Starting ${name}: ${event.title}`;
|
|
1642
|
+
}
|
|
1643
|
+
case "agent-session-ended":
|
|
1644
|
+
return event.success ? "\uD83E\uDD16 Agent session ended (success)" : `\uD83E\uDD16 Agent session ended (error: ${event.error})`;
|
|
1645
|
+
case "agent-session-activity":
|
|
1646
|
+
case "claude-event":
|
|
1647
|
+
return null;
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1543
1651
|
// lib/cli/commands/loop.ts
|
|
1544
1652
|
import { spawn as nodeSpawn2 } from "node:child_process";
|
|
1545
1653
|
import { readFileSync } from "node:fs";
|
|
1546
1654
|
import os from "node:os";
|
|
1547
|
-
import { dirname, join as
|
|
1655
|
+
import { dirname, join as join6 } from "node:path";
|
|
1548
1656
|
import { fileURLToPath } from "node:url";
|
|
1549
1657
|
|
|
1550
1658
|
// lib/workflow-tasks.ts
|
|
@@ -1634,7 +1742,11 @@ async function findUnblockedTasks(cwd, fileSystem) {
|
|
|
1634
1742
|
return { tasks: [] };
|
|
1635
1743
|
}
|
|
1636
1744
|
const files = await fileSystem.readdir(tasksPath);
|
|
1637
|
-
const mdFiles = files.filter((f) => f.endsWith(".md")).sort()
|
|
1745
|
+
const mdFiles = files.filter((f) => f.endsWith(".md")).sort((a, b) => {
|
|
1746
|
+
const aTime = fileSystem.getFileCreationTime(`${tasksPath}/${a}`);
|
|
1747
|
+
const bTime = fileSystem.getFileCreationTime(`${tasksPath}/${b}`);
|
|
1748
|
+
return aTime - bTime;
|
|
1749
|
+
});
|
|
1638
1750
|
if (mdFiles.length === 0) {
|
|
1639
1751
|
return { tasks: [] };
|
|
1640
1752
|
}
|
|
@@ -1688,8 +1800,8 @@ async function next(dependencies) {
|
|
|
1688
1800
|
var __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
1689
1801
|
function getDustVersion() {
|
|
1690
1802
|
const candidates = [
|
|
1691
|
-
|
|
1692
|
-
|
|
1803
|
+
join6(__dirname2, "../../../package.json"),
|
|
1804
|
+
join6(__dirname2, "../package.json")
|
|
1693
1805
|
];
|
|
1694
1806
|
for (const candidate of candidates) {
|
|
1695
1807
|
try {
|
|
@@ -1712,8 +1824,10 @@ function formatLoopEvent(event) {
|
|
|
1712
1824
|
switch (event.type) {
|
|
1713
1825
|
case "loop.warning":
|
|
1714
1826
|
return "⚠️ WARNING: This command skips all permission checks. Only use in a sandbox environment!";
|
|
1715
|
-
case "loop.started":
|
|
1716
|
-
|
|
1827
|
+
case "loop.started": {
|
|
1828
|
+
const agent2 = event.agentType ?? "claude";
|
|
1829
|
+
return `\uD83D\uDD04 Starting dust loop ${agent2} (max ${event.maxIterations} iterations)...`;
|
|
1830
|
+
}
|
|
1717
1831
|
case "loop.syncing":
|
|
1718
1832
|
return "\uD83C\uDF0D Syncing with remote";
|
|
1719
1833
|
case "loop.sync_skipped":
|
|
@@ -1799,6 +1913,7 @@ async function findAvailableTasks(dependencies) {
|
|
|
1799
1913
|
async function runOneIteration(dependencies, loopDependencies, onLoopEvent, onAgentEvent, options = {}) {
|
|
1800
1914
|
const { context } = dependencies;
|
|
1801
1915
|
const { spawn, run: run2 } = loopDependencies;
|
|
1916
|
+
const agentName = loopDependencies.agentType === "codex" ? "Codex" : "Claude";
|
|
1802
1917
|
const { onRawEvent } = options;
|
|
1803
1918
|
onLoopEvent({ type: "loop.syncing" });
|
|
1804
1919
|
const pullResult = await gitPull(context.cwd, spawn);
|
|
@@ -1823,7 +1938,7 @@ Make sure the repository is in a clean state and synced with remote before finis
|
|
|
1823
1938
|
type: "agent-session-started",
|
|
1824
1939
|
title: "Resolving git conflict",
|
|
1825
1940
|
prompt: prompt2,
|
|
1826
|
-
agentType: "claude",
|
|
1941
|
+
agentType: loopDependencies.agentType ?? "claude",
|
|
1827
1942
|
purpose: "git-conflict",
|
|
1828
1943
|
...getEnvironmentContext(context.cwd)
|
|
1829
1944
|
});
|
|
@@ -1840,7 +1955,7 @@ Make sure the repository is in a clean state and synced with remote before finis
|
|
|
1840
1955
|
return "resolved_pull_conflict";
|
|
1841
1956
|
} catch (error) {
|
|
1842
1957
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1843
|
-
context.stderr(
|
|
1958
|
+
context.stderr(`${agentName} failed to resolve git pull conflict: ${errorMessage}`);
|
|
1844
1959
|
onAgentEvent?.({
|
|
1845
1960
|
type: "agent-session-ended",
|
|
1846
1961
|
success: false,
|
|
@@ -1876,7 +1991,7 @@ ${instructions}`;
|
|
|
1876
1991
|
type: "agent-session-started",
|
|
1877
1992
|
title: task.title ?? task.path,
|
|
1878
1993
|
prompt,
|
|
1879
|
-
agentType: "claude",
|
|
1994
|
+
agentType: loopDependencies.agentType ?? "claude",
|
|
1880
1995
|
purpose: "task",
|
|
1881
1996
|
...getEnvironmentContext(context.cwd)
|
|
1882
1997
|
});
|
|
@@ -1893,7 +2008,7 @@ ${instructions}`;
|
|
|
1893
2008
|
return "ran_claude";
|
|
1894
2009
|
} catch (error) {
|
|
1895
2010
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1896
|
-
context.stderr(
|
|
2011
|
+
context.stderr(`${agentName} exited with error: ${errorMessage}`);
|
|
1897
2012
|
onAgentEvent?.({
|
|
1898
2013
|
type: "agent-session-ended",
|
|
1899
2014
|
success: false,
|
|
@@ -1937,7 +2052,11 @@ async function loopClaude(dependencies, loopDependencies = createDefaultDependen
|
|
|
1937
2052
|
sendWireEvent(event);
|
|
1938
2053
|
};
|
|
1939
2054
|
onLoopEvent({ type: "loop.warning" });
|
|
1940
|
-
onLoopEvent({
|
|
2055
|
+
onLoopEvent({
|
|
2056
|
+
type: "loop.started",
|
|
2057
|
+
maxIterations,
|
|
2058
|
+
agentType: loopDependencies.agentType
|
|
2059
|
+
});
|
|
1941
2060
|
context.stdout(" Press Ctrl+C to stop");
|
|
1942
2061
|
context.stdout("");
|
|
1943
2062
|
let completedIterations = 0;
|
|
@@ -1965,68 +2084,8 @@ async function loopClaude(dependencies, loopDependencies = createDefaultDependen
|
|
|
1965
2084
|
return { exitCode: 0 };
|
|
1966
2085
|
}
|
|
1967
2086
|
|
|
1968
|
-
// lib/bucket/repository.ts
|
|
1969
|
-
var
|
|
1970
|
-
function parseRepository(data) {
|
|
1971
|
-
if (typeof data === "string") {
|
|
1972
|
-
return { name: data, gitUrl: data };
|
|
1973
|
-
}
|
|
1974
|
-
if (typeof data === "object" && data !== null && "name" in data && "gitUrl" in data) {
|
|
1975
|
-
const repositoryData = data;
|
|
1976
|
-
if (typeof repositoryData.name === "string" && typeof repositoryData.gitUrl === "string") {
|
|
1977
|
-
const repo = {
|
|
1978
|
-
name: repositoryData.name,
|
|
1979
|
-
gitUrl: repositoryData.gitUrl
|
|
1980
|
-
};
|
|
1981
|
-
if (typeof repositoryData.url === "string") {
|
|
1982
|
-
repo.url = repositoryData.url;
|
|
1983
|
-
}
|
|
1984
|
-
return repo;
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
return null;
|
|
1988
|
-
}
|
|
1989
|
-
function getRepoPath(repoName, reposDir) {
|
|
1990
|
-
const safeName = repoName.replace(/[^a-zA-Z0-9-_/]/g, "-");
|
|
1991
|
-
return join6(reposDir, safeName);
|
|
1992
|
-
}
|
|
1993
|
-
async function cloneRepository(repository, targetPath, spawn, context) {
|
|
1994
|
-
return new Promise((resolve) => {
|
|
1995
|
-
const proc = spawn("git", ["clone", repository.gitUrl, targetPath], {
|
|
1996
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1997
|
-
});
|
|
1998
|
-
let stderr = "";
|
|
1999
|
-
proc.stderr?.on("data", (data) => {
|
|
2000
|
-
stderr += data.toString();
|
|
2001
|
-
});
|
|
2002
|
-
proc.on("close", (code) => {
|
|
2003
|
-
if (code === 0) {
|
|
2004
|
-
resolve(true);
|
|
2005
|
-
} else {
|
|
2006
|
-
context.stderr(`Failed to clone ${repository.name}: ${stderr.trim()}`);
|
|
2007
|
-
resolve(false);
|
|
2008
|
-
}
|
|
2009
|
-
});
|
|
2010
|
-
proc.on("error", (error) => {
|
|
2011
|
-
context.stderr(`Failed to clone ${repository.name}: ${error.message}`);
|
|
2012
|
-
resolve(false);
|
|
2013
|
-
});
|
|
2014
|
-
});
|
|
2015
|
-
}
|
|
2016
|
-
async function removeRepository(path, spawn, context) {
|
|
2017
|
-
return new Promise((resolve) => {
|
|
2018
|
-
const proc = spawn("rm", ["-rf", path], {
|
|
2019
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
2020
|
-
});
|
|
2021
|
-
proc.on("close", (code) => {
|
|
2022
|
-
resolve(code === 0);
|
|
2023
|
-
});
|
|
2024
|
-
proc.on("error", (error) => {
|
|
2025
|
-
context.stderr(`Failed to remove ${path}: ${error.message}`);
|
|
2026
|
-
resolve(false);
|
|
2027
|
-
});
|
|
2028
|
-
});
|
|
2029
|
-
}
|
|
2087
|
+
// lib/bucket/repository-loop.ts
|
|
2088
|
+
var FALLBACK_TIMEOUT_MS = 300000;
|
|
2030
2089
|
function createNoOpGlobScanner() {
|
|
2031
2090
|
return {
|
|
2032
2091
|
scan: async function* () {}
|
|
@@ -2120,11 +2179,42 @@ async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
|
|
|
2120
2179
|
}
|
|
2121
2180
|
});
|
|
2122
2181
|
if (result === "no_tasks") {
|
|
2123
|
-
await
|
|
2182
|
+
await new Promise((resolve) => {
|
|
2183
|
+
repoState.wakeUp = () => {
|
|
2184
|
+
repoState.wakeUp = undefined;
|
|
2185
|
+
resolve();
|
|
2186
|
+
};
|
|
2187
|
+
sleep(FALLBACK_TIMEOUT_MS).then(() => {
|
|
2188
|
+
if (repoState.wakeUp) {
|
|
2189
|
+
repoState.wakeUp = undefined;
|
|
2190
|
+
resolve();
|
|
2191
|
+
}
|
|
2192
|
+
});
|
|
2193
|
+
});
|
|
2124
2194
|
}
|
|
2125
2195
|
}
|
|
2126
2196
|
appendLogLine(repoState.logBuffer, createLogLine(`Stopped loop for ${repoName}`, "stdout"));
|
|
2127
2197
|
}
|
|
2198
|
+
// lib/bucket/repository.ts
|
|
2199
|
+
function parseRepository(data) {
|
|
2200
|
+
if (typeof data === "string") {
|
|
2201
|
+
return { name: data, gitUrl: data };
|
|
2202
|
+
}
|
|
2203
|
+
if (typeof data === "object" && data !== null && "name" in data && "gitUrl" in data) {
|
|
2204
|
+
const repositoryData = data;
|
|
2205
|
+
if (typeof repositoryData.name === "string" && typeof repositoryData.gitUrl === "string") {
|
|
2206
|
+
const repo = {
|
|
2207
|
+
name: repositoryData.name,
|
|
2208
|
+
gitUrl: repositoryData.gitUrl
|
|
2209
|
+
};
|
|
2210
|
+
if (typeof repositoryData.url === "string") {
|
|
2211
|
+
repo.url = repositoryData.url;
|
|
2212
|
+
}
|
|
2213
|
+
return repo;
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
return null;
|
|
2217
|
+
}
|
|
2128
2218
|
async function addRepository(repository, manager, repoDeps, context) {
|
|
2129
2219
|
if (manager.repositories.has(repository.name)) {
|
|
2130
2220
|
return;
|
|
@@ -2169,6 +2259,7 @@ async function removeRepositoryFromManager(repoName, manager, repoDeps, context)
|
|
|
2169
2259
|
return;
|
|
2170
2260
|
}
|
|
2171
2261
|
repoState.stopRequested = true;
|
|
2262
|
+
repoState.wakeUp?.();
|
|
2172
2263
|
if (repoState.loopPromise) {
|
|
2173
2264
|
await Promise.race([repoState.loopPromise, repoDeps.sleep(5000)]);
|
|
2174
2265
|
}
|
|
@@ -2714,6 +2805,7 @@ function createDefaultBucketDependencies() {
|
|
|
2714
2805
|
return false;
|
|
2715
2806
|
}
|
|
2716
2807
|
},
|
|
2808
|
+
getFileCreationTime: (path) => statSync(path).birthtimeMs,
|
|
2717
2809
|
readFile: (path) => readFile(path, "utf8"),
|
|
2718
2810
|
writeFile: (path, content) => writeFile(path, content, "utf8"),
|
|
2719
2811
|
mkdir: (path, options) => mkdir(path, options).then(() => {}),
|
|
@@ -2730,7 +2822,7 @@ function createDefaultBucketDependencies() {
|
|
|
2730
2822
|
writeStdout: defaultWriteStdout,
|
|
2731
2823
|
isTTY: process.stdout.isTTY ?? false,
|
|
2732
2824
|
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
2733
|
-
getReposDir: () => process.env.DUST_REPOS_DIR ||
|
|
2825
|
+
getReposDir: () => process.env.DUST_REPOS_DIR || join8(homedir(), ".dust", "repos"),
|
|
2734
2826
|
auth: {
|
|
2735
2827
|
createServer: defaultCreateServer,
|
|
2736
2828
|
openBrowser: defaultOpenBrowser,
|
|
@@ -2876,6 +2968,10 @@ function connectWebSocket(token, state, bucketDependencies, context, fileSystem,
|
|
|
2876
2968
|
state.emit(disconnectEvent);
|
|
2877
2969
|
logMessage(state, context, useTUI, formatBucketEvent(disconnectEvent));
|
|
2878
2970
|
state.ws = null;
|
|
2971
|
+
if (event.code === 4000) {
|
|
2972
|
+
logMessage(state, context, useTUI, "Another connection replaced this one. Not reconnecting.");
|
|
2973
|
+
return;
|
|
2974
|
+
}
|
|
2879
2975
|
if (!state.shuttingDown) {
|
|
2880
2976
|
logMessage(state, context, useTUI, `Reconnecting in ${state.reconnectDelay / 1000} seconds...`);
|
|
2881
2977
|
state.reconnectTimer = setTimeout(() => {
|
|
@@ -2896,9 +2992,23 @@ function connectWebSocket(token, state, bucketDependencies, context, fileSystem,
|
|
|
2896
2992
|
syncUIWithRepoList(state, repos);
|
|
2897
2993
|
const repoDeps = toRepositoryDependencies(bucketDependencies, fileSystem);
|
|
2898
2994
|
const repoContext = createTUIContext(state, context, useTUI);
|
|
2899
|
-
handleRepositoryList(repos, state, repoDeps, repoContext).then(() =>
|
|
2995
|
+
handleRepositoryList(repos, state, repoDeps, repoContext).then(() => {
|
|
2996
|
+
syncTUI(state);
|
|
2997
|
+
for (const repoData of repos) {
|
|
2998
|
+
if (typeof repoData === "object" && repoData !== null && "name" in repoData && "hasTask" in repoData && repoData.hasTask) {
|
|
2999
|
+
const repoState = state.repositories.get(repoData.name);
|
|
3000
|
+
repoState?.wakeUp?.();
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
}).catch((error) => {
|
|
2900
3004
|
logMessage(state, context, useTUI, `Failed to handle repository list: ${error.message}`, "stderr");
|
|
2901
3005
|
});
|
|
3006
|
+
} else if (message.type === "task-available") {
|
|
3007
|
+
const repoName = message.repository;
|
|
3008
|
+
if (typeof repoName === "string") {
|
|
3009
|
+
const repoState = state.repositories.get(repoName);
|
|
3010
|
+
repoState?.wakeUp?.();
|
|
3011
|
+
}
|
|
2902
3012
|
}
|
|
2903
3013
|
} catch {
|
|
2904
3014
|
logMessage(state, context, useTUI, `Failed to parse WebSocket message: ${event.data}`, "stderr");
|
|
@@ -2920,6 +3030,7 @@ async function shutdown(state, bucketDeps, context) {
|
|
|
2920
3030
|
}
|
|
2921
3031
|
for (const repoState of state.repositories.values()) {
|
|
2922
3032
|
repoState.stopRequested = true;
|
|
3033
|
+
repoState.wakeUp?.();
|
|
2923
3034
|
}
|
|
2924
3035
|
const loopPromises = Array.from(state.repositories.values()).map((rs) => rs.loopPromise).filter((p) => p !== null);
|
|
2925
3036
|
await Promise.all(loopPromises.map((p) => p.catch(() => {})));
|
|
@@ -3099,7 +3210,7 @@ function runBufferedProcess(spawnFn, command, commandArguments, cwd, shell, time
|
|
|
3099
3210
|
}
|
|
3100
3211
|
|
|
3101
3212
|
// lib/cli/commands/lint-markdown.ts
|
|
3102
|
-
import { dirname as dirname3, join as
|
|
3213
|
+
import { dirname as dirname3, join as join9, resolve } from "node:path";
|
|
3103
3214
|
var REQUIRED_HEADINGS = ["## Goals", "## Blocked By", "## Definition of Done"];
|
|
3104
3215
|
var REQUIRED_GOAL_HEADINGS = ["## Parent Goal", "## Sub-Goals"];
|
|
3105
3216
|
var SLUG_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*\.md$/;
|
|
@@ -3636,7 +3747,7 @@ async function lintMarkdown(dependencies) {
|
|
|
3636
3747
|
const violations = [];
|
|
3637
3748
|
context.stdout("Validating directory structure...");
|
|
3638
3749
|
violations.push(...await validateDirectoryStructure(dustPath, fileSystem, dependencies.settings.extraDirectories));
|
|
3639
|
-
const settingsPath =
|
|
3750
|
+
const settingsPath = join9(dustPath, "config", "settings.json");
|
|
3640
3751
|
if (fileSystem.exists(settingsPath)) {
|
|
3641
3752
|
context.stdout("Validating settings.json...");
|
|
3642
3753
|
try {
|
|
@@ -4225,6 +4336,125 @@ async function list(dependencies) {
|
|
|
4225
4336
|
return { exitCode: 0 };
|
|
4226
4337
|
}
|
|
4227
4338
|
|
|
4339
|
+
// lib/codex/spawn-codex.ts
|
|
4340
|
+
import { spawn as nodeSpawn4 } from "node:child_process";
|
|
4341
|
+
import { createInterface as nodeCreateInterface2 } from "node:readline";
|
|
4342
|
+
var defaultDependencies2 = {
|
|
4343
|
+
spawn: nodeSpawn4,
|
|
4344
|
+
createInterface: nodeCreateInterface2
|
|
4345
|
+
};
|
|
4346
|
+
async function* spawnCodex(prompt, options = {}, dependencies = defaultDependencies2) {
|
|
4347
|
+
const { cwd, env } = options;
|
|
4348
|
+
const codexArguments = ["exec", prompt, "--json", "--yolo"];
|
|
4349
|
+
if (cwd) {
|
|
4350
|
+
codexArguments.push("--cd", cwd);
|
|
4351
|
+
}
|
|
4352
|
+
const proc = dependencies.spawn("codex", codexArguments, {
|
|
4353
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
4354
|
+
env: { ...process.env, ...env }
|
|
4355
|
+
});
|
|
4356
|
+
if (!proc.stdout) {
|
|
4357
|
+
throw new Error("Failed to get stdout from codex process");
|
|
4358
|
+
}
|
|
4359
|
+
const rl = dependencies.createInterface({ input: proc.stdout });
|
|
4360
|
+
for await (const line of rl) {
|
|
4361
|
+
if (!line.trim())
|
|
4362
|
+
continue;
|
|
4363
|
+
try {
|
|
4364
|
+
yield JSON.parse(line);
|
|
4365
|
+
} catch {}
|
|
4366
|
+
}
|
|
4367
|
+
let stderrOutput = "";
|
|
4368
|
+
proc.stderr?.on("data", (data) => {
|
|
4369
|
+
stderrOutput += data.toString();
|
|
4370
|
+
});
|
|
4371
|
+
await new Promise((resolve2, reject) => {
|
|
4372
|
+
proc.on("close", (code) => {
|
|
4373
|
+
if (code === 0 || code === null)
|
|
4374
|
+
resolve2();
|
|
4375
|
+
else {
|
|
4376
|
+
const errMsg = stderrOutput.trim() ? `codex exited with code ${code}: ${stderrOutput.trim()}` : `codex exited with code ${code}`;
|
|
4377
|
+
reject(new Error(errMsg));
|
|
4378
|
+
}
|
|
4379
|
+
});
|
|
4380
|
+
proc.on("error", reject);
|
|
4381
|
+
});
|
|
4382
|
+
}
|
|
4383
|
+
|
|
4384
|
+
// lib/codex/event-parser.ts
|
|
4385
|
+
function* parseCodexRawEvent(raw) {
|
|
4386
|
+
if (raw.type !== "item.completed")
|
|
4387
|
+
return;
|
|
4388
|
+
const item = raw.item;
|
|
4389
|
+
if (!item)
|
|
4390
|
+
return;
|
|
4391
|
+
if (item.type === "agent_message" && typeof item.text === "string") {
|
|
4392
|
+
yield { type: "text_delta", text: `${item.text}
|
|
4393
|
+
` };
|
|
4394
|
+
} else if (item.type === "command_execution" && typeof item.command === "string") {
|
|
4395
|
+
yield {
|
|
4396
|
+
type: "tool_use",
|
|
4397
|
+
id: typeof item.id === "string" ? item.id : "",
|
|
4398
|
+
name: "command_execution",
|
|
4399
|
+
input: { command: item.command }
|
|
4400
|
+
};
|
|
4401
|
+
if (typeof item.aggregated_output === "string") {
|
|
4402
|
+
yield {
|
|
4403
|
+
type: "tool_result",
|
|
4404
|
+
toolUseId: typeof item.id === "string" ? item.id : "",
|
|
4405
|
+
content: item.aggregated_output || `(exit code: ${item.exit_code ?? "unknown"})`
|
|
4406
|
+
};
|
|
4407
|
+
}
|
|
4408
|
+
}
|
|
4409
|
+
}
|
|
4410
|
+
|
|
4411
|
+
// lib/codex/streamer.ts
|
|
4412
|
+
async function streamCodexEvents(events, sink, onRawEvent) {
|
|
4413
|
+
let hadTextOutput = false;
|
|
4414
|
+
for await (const raw of events) {
|
|
4415
|
+
onRawEvent?.(raw);
|
|
4416
|
+
for (const event of parseCodexRawEvent(raw)) {
|
|
4417
|
+
processEvent(event, sink, { hadTextOutput });
|
|
4418
|
+
if (event.type === "text_delta") {
|
|
4419
|
+
hadTextOutput = true;
|
|
4420
|
+
} else if (event.type === "tool_use") {
|
|
4421
|
+
hadTextOutput = false;
|
|
4422
|
+
}
|
|
4423
|
+
}
|
|
4424
|
+
}
|
|
4425
|
+
}
|
|
4426
|
+
|
|
4427
|
+
// lib/codex/run.ts
|
|
4428
|
+
var defaultRunnerDependencies2 = {
|
|
4429
|
+
spawnCodex,
|
|
4430
|
+
createStdoutSink,
|
|
4431
|
+
streamCodexEvents
|
|
4432
|
+
};
|
|
4433
|
+
async function run2(prompt, options = {}, dependencies = defaultRunnerDependencies2) {
|
|
4434
|
+
const isRunOptions = (opt) => ("spawnOptions" in opt) || ("onRawEvent" in opt);
|
|
4435
|
+
const spawnOptions = isRunOptions(options) ? options.spawnOptions ?? {} : options;
|
|
4436
|
+
const onRawEvent = isRunOptions(options) ? options.onRawEvent : undefined;
|
|
4437
|
+
const events = dependencies.spawnCodex(prompt, spawnOptions);
|
|
4438
|
+
const sink = dependencies.createStdoutSink();
|
|
4439
|
+
await dependencies.streamCodexEvents(events, sink, onRawEvent);
|
|
4440
|
+
}
|
|
4441
|
+
|
|
4442
|
+
// lib/cli/commands/loop-codex.ts
|
|
4443
|
+
function createCodexDependencies(overrides = {}) {
|
|
4444
|
+
return {
|
|
4445
|
+
...createDefaultDependencies(),
|
|
4446
|
+
run: run2,
|
|
4447
|
+
...overrides,
|
|
4448
|
+
agentType: "codex"
|
|
4449
|
+
};
|
|
4450
|
+
}
|
|
4451
|
+
async function loopCodex(dependencies, loopDependencies = createCodexDependencies()) {
|
|
4452
|
+
return loopClaude(dependencies, {
|
|
4453
|
+
...loopDependencies,
|
|
4454
|
+
agentType: "codex"
|
|
4455
|
+
});
|
|
4456
|
+
}
|
|
4457
|
+
|
|
4228
4458
|
// lib/cli/commands/new-goal.ts
|
|
4229
4459
|
function newGoalInstructions(vars) {
|
|
4230
4460
|
const intro = vars.isClaudeCodeWeb ? "Follow these steps. Use a todo list to track your progress." : "Follow these steps:";
|
|
@@ -4555,6 +4785,7 @@ var commandRegistry = {
|
|
|
4555
4785
|
"implement task": implementTask,
|
|
4556
4786
|
"pick task": pickTask,
|
|
4557
4787
|
"loop claude": loopClaude,
|
|
4788
|
+
"loop codex": loopCodex,
|
|
4558
4789
|
"pre push": prePush,
|
|
4559
4790
|
help
|
|
4560
4791
|
};
|
|
@@ -4621,6 +4852,7 @@ function createFileSystem(primitives) {
|
|
|
4621
4852
|
mkdir: async (path, options) => {
|
|
4622
4853
|
await primitives.mkdir(path, options);
|
|
4623
4854
|
},
|
|
4855
|
+
getFileCreationTime: (path) => primitives.statSync(path).birthtimeMs,
|
|
4624
4856
|
readdir: (path) => primitives.readdir(path),
|
|
4625
4857
|
chmod: (path, mode) => primitives.chmod(path, mode)
|
|
4626
4858
|
};
|