codework 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 +41 -0
- package/dist/cli.js +384 -57
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,6 +26,47 @@ Then agents should repeatedly use:
|
|
|
26
26
|
! codework done --workspace=my-workspace --name=<agent> --summary="..."
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
## Waiting for another agent
|
|
30
|
+
|
|
31
|
+
`codework poll` is a bounded long-poll command for LLM agents.
|
|
32
|
+
|
|
33
|
+
If no actionable event arrives, Codework intentionally exits and tells the agent to run the same command again. This is by design: Codework stdout is injected into the agent's context, so the retry instruction must be visible to the model.
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
! codework poll --workspace=my-workspace --name=claude --wait=30
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
If no directive arrives, the output will include:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
MUST: Run the exact command below again.
|
|
45
|
+
RE-RUN EXACT COMMAND:
|
|
46
|
+
codework poll --workspace=my-workspace --name=claude --wait=30 --interval=2
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Do not interpret an empty poll as task completion.
|
|
50
|
+
|
|
51
|
+
### Waiting-loop demo
|
|
52
|
+
|
|
53
|
+
Claude can join before Codex posts a directive:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
! codework join --workspace=todo-demo --name=claude --follow=codex
|
|
57
|
+
! codework poll --workspace=todo-demo --name=claude --wait=30
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
If no actionable event has arrived, Codework returns `WAIT CONTINUATION REQUIRED` and tells Claude to run the same poll command again. Claude must keep repeating that exact command until a directive, question, blocker, or other actionable event appears.
|
|
61
|
+
|
|
62
|
+
Then Codex can post work:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
! codework say --workspace=todo-demo --name=codex --to=claude --kind=directive --message="Implement the Todo app and verify with npm run build."
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Claude's next poll receives the directive and switches from waiting to handling the actionable event.
|
|
69
|
+
|
|
29
70
|
## Runtime support
|
|
30
71
|
|
|
31
72
|
Node.js:
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { Command, CommanderError } from "commander";
|
|
5
5
|
import { realpathSync } from "fs";
|
|
6
|
-
import
|
|
6
|
+
import process6 from "process";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
8
|
|
|
9
9
|
// src/args.ts
|
|
@@ -108,6 +108,20 @@ function parsePositiveInteger(value, optionName, fallback) {
|
|
|
108
108
|
}
|
|
109
109
|
return parsed;
|
|
110
110
|
}
|
|
111
|
+
function parseBoundedSeconds(input) {
|
|
112
|
+
const value = input.cliValue ?? input.envValue;
|
|
113
|
+
if (value === void 0 || value.trim() === "") {
|
|
114
|
+
return input.fallback;
|
|
115
|
+
}
|
|
116
|
+
const parsed = Number(value);
|
|
117
|
+
if (!Number.isFinite(parsed) || parsed < input.min || parsed > input.max) {
|
|
118
|
+
throw new CodeworkError(
|
|
119
|
+
2,
|
|
120
|
+
`Invalid ${input.optionName}: expected seconds between ${input.min} and ${input.max}.`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
return parsed;
|
|
124
|
+
}
|
|
111
125
|
|
|
112
126
|
// src/core/state.ts
|
|
113
127
|
function nowIso() {
|
|
@@ -313,7 +327,19 @@ function isOwnOperationalEvent(event, agent) {
|
|
|
313
327
|
}
|
|
314
328
|
function relevantUnreadEvents(state, events, agent, since) {
|
|
315
329
|
const cursor = since ?? agent.cursorEventId;
|
|
316
|
-
return events.filter((event) => event.id > cursor).filter((event) => targetMatchesAgent(event.to, agent)).filter((event) => !isOwnOperationalEvent(event, agent)).sort((a, b) => priorityForEvent(state, agent, b) - priorityForEvent(state, agent, a) || a.id - b.id);
|
|
330
|
+
return events.filter((event) => event.id > cursor).filter((event) => targetMatchesAgent(event.to, agent) || event.actor === agent.follow).filter((event) => !isOwnOperationalEvent(event, agent)).sort((a, b) => priorityForEvent(state, agent, b) - priorityForEvent(state, agent, a) || a.id - b.id);
|
|
331
|
+
}
|
|
332
|
+
function isActionableEvent(event, agent) {
|
|
333
|
+
if (event.type === "warning.created") {
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
if (event.type === "message.posted") {
|
|
337
|
+
return event.kind === "directive" || event.kind === "question" || event.kind === "blocker";
|
|
338
|
+
}
|
|
339
|
+
if (event.type === "work.done") {
|
|
340
|
+
return agent.follow !== "user" && agent.follow !== "self" && event.actor === agent.follow;
|
|
341
|
+
}
|
|
342
|
+
return false;
|
|
317
343
|
}
|
|
318
344
|
function priorityForEvent(state, agent, event) {
|
|
319
345
|
const follow = agent.follow;
|
|
@@ -341,11 +367,6 @@ function latestEvents(events, tail) {
|
|
|
341
367
|
}
|
|
342
368
|
return events.slice(Math.max(0, events.length - tail));
|
|
343
369
|
}
|
|
344
|
-
function advanceCursorToLatest(state, agent) {
|
|
345
|
-
agent.cursorEventId = latestEventId(state);
|
|
346
|
-
agent.lastSeenAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
347
|
-
state.updatedAt = agent.lastSeenAt;
|
|
348
|
-
}
|
|
349
370
|
function formatEvent(event) {
|
|
350
371
|
const parts = [
|
|
351
372
|
`id=${event.id}`,
|
|
@@ -481,13 +502,34 @@ function resolveCodeworkPaths(options) {
|
|
|
481
502
|
};
|
|
482
503
|
}
|
|
483
504
|
|
|
505
|
+
// src/core/command.ts
|
|
506
|
+
function shellQuote(value) {
|
|
507
|
+
if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(value)) {
|
|
508
|
+
return value;
|
|
509
|
+
}
|
|
510
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
511
|
+
}
|
|
512
|
+
function buildCanonicalPollCommand(input) {
|
|
513
|
+
return [
|
|
514
|
+
"codework",
|
|
515
|
+
input.kind,
|
|
516
|
+
`--workspace=${shellQuote(input.workspace)}`,
|
|
517
|
+
`--name=${shellQuote(input.name)}`,
|
|
518
|
+
`--wait=${formatSeconds(input.waitSeconds)}`,
|
|
519
|
+
`--interval=${formatSeconds(input.intervalSeconds)}`
|
|
520
|
+
].join(" ");
|
|
521
|
+
}
|
|
522
|
+
function formatSeconds(value) {
|
|
523
|
+
return Number.isInteger(value) ? String(value) : String(value);
|
|
524
|
+
}
|
|
525
|
+
|
|
484
526
|
// src/core/render.ts
|
|
485
527
|
function renderGuide(options) {
|
|
486
528
|
const agent = findAgent(options.state, options.agentName);
|
|
487
529
|
const agentName = agent?.name ?? options.agentName ?? "(not specified)";
|
|
488
530
|
const follow = agent?.follow ?? "(none)";
|
|
489
531
|
const workspace = options.state.workspace;
|
|
490
|
-
const recommendedCommand = options.recommendedCommand ?? (agent ?
|
|
532
|
+
const recommendedCommand = options.recommendedCommand ?? (agent ? defaultPollCommand(workspace, agent.name) : `codework status --workspace=${workspace}`);
|
|
491
533
|
const lines = [
|
|
492
534
|
"# CODEWORK CONTEXT GUIDE",
|
|
493
535
|
"",
|
|
@@ -527,10 +569,11 @@ function renderGuide(options) {
|
|
|
527
569
|
`You currently follow: ${follow}.`,
|
|
528
570
|
"Interpretation:",
|
|
529
571
|
...followInterpretationLines(options.state, follow),
|
|
572
|
+
...waitingForFollowTargetLines(options.state, agent, workspace),
|
|
530
573
|
"",
|
|
531
574
|
"## REQUIRED OPERATING LOOP",
|
|
532
575
|
"",
|
|
533
|
-
`1. Run
|
|
576
|
+
`1. Run \`${agent ? defaultPollCommand(workspace, agent.name) : `codework poll --workspace=${workspace} --name=${agentName} --wait=30 --interval=2`}\` before starting a new substantial step.`,
|
|
534
577
|
"2. Read blockers first.",
|
|
535
578
|
"3. If you need another agent, post a directive:",
|
|
536
579
|
` \`codework say --workspace=${workspace} --name=${agentName} --to=<agent> --kind=directive --message="..."\``,
|
|
@@ -553,7 +596,7 @@ function renderGuide(options) {
|
|
|
553
596
|
"",
|
|
554
597
|
"## UNREAD EVENTS",
|
|
555
598
|
"",
|
|
556
|
-
...eventLines(options.unreadEvents, "No unread events."),
|
|
599
|
+
...eventLines(options.unreadEvents, "No actionable unread events are shown here. Use the poll command below to wait for peer events."),
|
|
557
600
|
"",
|
|
558
601
|
"## WARNINGS",
|
|
559
602
|
"",
|
|
@@ -579,7 +622,8 @@ function renderGuide(options) {
|
|
|
579
622
|
"",
|
|
580
623
|
`- codework guide --workspace=${workspace} --name=${agentName}`,
|
|
581
624
|
`- codework status --workspace=${workspace} --name=${agentName}`,
|
|
582
|
-
`- codework poll --workspace=${workspace} --name=${agentName}`,
|
|
625
|
+
`- codework poll --workspace=${workspace} --name=${agentName} --wait=30 --interval=2`,
|
|
626
|
+
`- codework wait --workspace=${workspace} --name=${agentName} --wait=30 --interval=2`,
|
|
583
627
|
`- codework say --workspace=${workspace} --name=${agentName} --to=<agent|all> --kind=<note|directive|question|blocker> --message="..."`,
|
|
584
628
|
`- codework done --workspace=${workspace} --name=${agentName} --summary="..." --tests="..."`,
|
|
585
629
|
`- codework log --workspace=${workspace} --tail=20`
|
|
@@ -587,6 +631,93 @@ function renderGuide(options) {
|
|
|
587
631
|
return `${lines.join("\n")}
|
|
588
632
|
`;
|
|
589
633
|
}
|
|
634
|
+
function renderPollResult(options) {
|
|
635
|
+
const follow = options.agent.follow;
|
|
636
|
+
const lines = [
|
|
637
|
+
options.title,
|
|
638
|
+
"",
|
|
639
|
+
`WORKSPACE: ${options.state.workspace}`,
|
|
640
|
+
`AGENT: ${options.agent.name}`,
|
|
641
|
+
`FOLLOW: ${follow}`,
|
|
642
|
+
`AUTHORITY MODE: ${authorityMode(follow)}`,
|
|
643
|
+
`TIMESTAMP: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
644
|
+
`POLL WINDOW: ${formatPollSeconds(options.waitSeconds)}`,
|
|
645
|
+
`POLL INTERVAL: ${formatPollSeconds(options.intervalSeconds)}`,
|
|
646
|
+
`POLL RESULT: ${options.pollResult}`,
|
|
647
|
+
`CONSECUTIVE EMPTY POLLS: ${options.agent.lastPollEmptyCount ?? 0}`,
|
|
648
|
+
"",
|
|
649
|
+
"## TEAM STATE",
|
|
650
|
+
"",
|
|
651
|
+
...teamStateLines(options.state),
|
|
652
|
+
"",
|
|
653
|
+
"## FOLLOW RULE",
|
|
654
|
+
"",
|
|
655
|
+
`You currently follow: ${follow}.`,
|
|
656
|
+
...pollFollowRuleLines(follow)
|
|
657
|
+
];
|
|
658
|
+
if (options.actionableEvents.length > 0) {
|
|
659
|
+
lines.push(
|
|
660
|
+
"",
|
|
661
|
+
"## ACTIONABLE EVENTS",
|
|
662
|
+
"",
|
|
663
|
+
...structuredEventLines(options.actionableEvents),
|
|
664
|
+
"",
|
|
665
|
+
"## REQUIRED NEXT ACTION",
|
|
666
|
+
"",
|
|
667
|
+
"MUST: Handle the actionable event above before polling again.",
|
|
668
|
+
follow !== "user" && follow !== "self" ? `MUST: Follow directives from ${follow} because your follow target is ${follow}.` : "MUST: Apply the actionable event according to your authority mode and the human's current instruction.",
|
|
669
|
+
`MUST: If blocked, send \`codework say --workspace=${options.state.workspace} --name=${options.agent.name} --to=${followTargetForCommand(follow)} --kind=blocker --message="..."\``,
|
|
670
|
+
`MUST: If complete, send \`codework done --workspace=${options.state.workspace} --name=${options.agent.name} --summary="..." --tests="..."\``,
|
|
671
|
+
"MUST NOT: ignore this directive and continue waiting.",
|
|
672
|
+
"",
|
|
673
|
+
"NEXT POLL COMMAND AFTER HANDLING:",
|
|
674
|
+
options.nextPollCommand
|
|
675
|
+
);
|
|
676
|
+
} else {
|
|
677
|
+
lines.push(
|
|
678
|
+
"",
|
|
679
|
+
"## NO ACTIONABLE EVENTS",
|
|
680
|
+
"",
|
|
681
|
+
"No actionable events arrived during this poll window."
|
|
682
|
+
);
|
|
683
|
+
if (options.nonActionableEvents.length > 0) {
|
|
684
|
+
lines.push(
|
|
685
|
+
"",
|
|
686
|
+
"## OBSERVED NON-ACTIONABLE EVENTS",
|
|
687
|
+
"",
|
|
688
|
+
...structuredEventLines(options.nonActionableEvents)
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
lines.push(
|
|
692
|
+
"",
|
|
693
|
+
"## WAIT CONTINUATION REQUIRED",
|
|
694
|
+
"",
|
|
695
|
+
"STATE: waiting-for-peer-event",
|
|
696
|
+
"MUST: Run the exact command below again.",
|
|
697
|
+
"MUST: Continue repeating it until an actionable event appears, the human gives a new instruction, or you explicitly report that you are still waiting.",
|
|
698
|
+
"MUST NOT: treat this empty poll as task completion.",
|
|
699
|
+
"MUST NOT: invent teammate state.",
|
|
700
|
+
"MUST NOT: start unrelated work merely because no event arrived.",
|
|
701
|
+
"",
|
|
702
|
+
"RE-RUN EXACT COMMAND:",
|
|
703
|
+
options.canonicalCommand,
|
|
704
|
+
"",
|
|
705
|
+
"## REPORTING RULE",
|
|
706
|
+
"",
|
|
707
|
+
"If you decide to speak to the human while still waiting, use this exact report shape:",
|
|
708
|
+
"",
|
|
709
|
+
"CODEWORK WAITING REPORT:",
|
|
710
|
+
`- Agent: ${options.agent.name}`,
|
|
711
|
+
`- Workspace: ${options.state.workspace}`,
|
|
712
|
+
`- Waiting for: ${waitingForText(follow)}`,
|
|
713
|
+
`- Consecutive empty polls: ${options.agent.lastPollEmptyCount ?? 0}`,
|
|
714
|
+
`- Last poll command: ${options.canonicalCommand}`,
|
|
715
|
+
"- Next action: run the same poll command again"
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
return `${lines.join("\n")}
|
|
719
|
+
`;
|
|
720
|
+
}
|
|
590
721
|
function renderLog(workspace, events) {
|
|
591
722
|
return [
|
|
592
723
|
"# CODEWORK EVENT LOG",
|
|
@@ -685,7 +816,8 @@ function teamStateLines(state) {
|
|
|
685
816
|
}
|
|
686
817
|
return agents.map((agent) => {
|
|
687
818
|
const active = agent.active ? "active" : "inactive";
|
|
688
|
-
|
|
819
|
+
const poll = agent.lastPollAt ? `, lastPoll=${agent.lastPollAt}, emptyPolls=${agent.lastPollEmptyCount ?? 0}` : "";
|
|
820
|
+
return `- ${agent.name}: ${active}, follow=${agent.follow}, role=${agent.role ?? "(none)"}, lastSeen=${agent.lastSeenAt}, sessions=${agent.sessionCount}, cursor=${agent.cursorEventId}${poll}`;
|
|
689
821
|
});
|
|
690
822
|
}
|
|
691
823
|
function followGraphLines(state) {
|
|
@@ -732,6 +864,96 @@ function warningLines(state) {
|
|
|
732
864
|
}
|
|
733
865
|
return state.warnings.map((warning) => `- ${warning.code}: ${warning.message} (${warning.createdAt})`);
|
|
734
866
|
}
|
|
867
|
+
function waitingForFollowTargetLines(state, agent, workspace) {
|
|
868
|
+
if (!agent || agent.follow === "user" || agent.follow === "self") {
|
|
869
|
+
return [];
|
|
870
|
+
}
|
|
871
|
+
const command = defaultPollCommand(workspace, agent.name);
|
|
872
|
+
return [
|
|
873
|
+
"",
|
|
874
|
+
"## WAITING FOR FOLLOW TARGET",
|
|
875
|
+
"",
|
|
876
|
+
`STATE: waiting-for-directive-from-${agent.follow}`,
|
|
877
|
+
`MUST: Poll for directives from ${agent.follow} before starting implementation.`,
|
|
878
|
+
"MUST: Run the command below now.",
|
|
879
|
+
`MUST NOT: begin implementation based only on the human's vague request if ${agent.follow} is your follow target.`,
|
|
880
|
+
"",
|
|
881
|
+
"RUN COMMAND:",
|
|
882
|
+
command,
|
|
883
|
+
"",
|
|
884
|
+
findAgentByFollowValue(state, agent.follow) ? `FOLLOW TARGET STATUS: ${agent.follow} is registered.` : `FOLLOW TARGET STATUS: ${agent.follow} is not registered yet; keep polling until directives arrive.`
|
|
885
|
+
];
|
|
886
|
+
}
|
|
887
|
+
function defaultPollCommand(workspace, name) {
|
|
888
|
+
return buildCanonicalPollCommand({
|
|
889
|
+
kind: "poll",
|
|
890
|
+
workspace,
|
|
891
|
+
name,
|
|
892
|
+
waitSeconds: 30,
|
|
893
|
+
intervalSeconds: 2
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
function pollFollowRuleLines(follow) {
|
|
897
|
+
if (follow === "user") {
|
|
898
|
+
return [
|
|
899
|
+
"MUST: Wait for directives, questions, blockers, or completion signals from the workspace.",
|
|
900
|
+
"MUST NOT: infer teammate state from silence."
|
|
901
|
+
];
|
|
902
|
+
}
|
|
903
|
+
if (follow === "self") {
|
|
904
|
+
return [
|
|
905
|
+
"MUST: Wait for actionable peer events before making claims about teammate progress.",
|
|
906
|
+
"MUST NOT: infer teammate state from silence."
|
|
907
|
+
];
|
|
908
|
+
}
|
|
909
|
+
return [
|
|
910
|
+
`MUST: Wait for directives, questions, blockers, or completion signals from ${follow}.`,
|
|
911
|
+
`MUST NOT: infer ${follow}'s intent from silence.`
|
|
912
|
+
];
|
|
913
|
+
}
|
|
914
|
+
function structuredEventLines(events) {
|
|
915
|
+
return events.flatMap((event) => {
|
|
916
|
+
const lines = [
|
|
917
|
+
`Event ${event.id}:`,
|
|
918
|
+
`- TYPE: ${event.type}`,
|
|
919
|
+
`- FROM: ${event.actor}`,
|
|
920
|
+
`- TO: ${event.to ?? "all"}`
|
|
921
|
+
];
|
|
922
|
+
if (event.kind) {
|
|
923
|
+
lines.push(`- KIND: ${event.kind}`);
|
|
924
|
+
}
|
|
925
|
+
const message = eventText(event);
|
|
926
|
+
if (message !== "") {
|
|
927
|
+
lines.push("- MESSAGE:", ...indentMultiline(message));
|
|
928
|
+
}
|
|
929
|
+
return [...lines, ""];
|
|
930
|
+
}).slice(0, -1);
|
|
931
|
+
}
|
|
932
|
+
function eventText(event) {
|
|
933
|
+
const keys = ["message", "summary", "blockers", "next", "reason"];
|
|
934
|
+
for (const key of keys) {
|
|
935
|
+
const value = event.payload[key];
|
|
936
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
937
|
+
return value;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
return JSON.stringify(event.payload);
|
|
941
|
+
}
|
|
942
|
+
function indentMultiline(value) {
|
|
943
|
+
return value.split(/\r?\n/).map((line) => ` ${line}`);
|
|
944
|
+
}
|
|
945
|
+
function followTargetForCommand(follow) {
|
|
946
|
+
return follow === "self" || follow === "user" ? "all" : follow;
|
|
947
|
+
}
|
|
948
|
+
function waitingForText(follow) {
|
|
949
|
+
if (follow === "user" || follow === "self") {
|
|
950
|
+
return "actionable workspace event";
|
|
951
|
+
}
|
|
952
|
+
return `directive or actionable event from ${follow}`;
|
|
953
|
+
}
|
|
954
|
+
function formatPollSeconds(value) {
|
|
955
|
+
return `${Number.isInteger(value) ? value : String(value)}s`;
|
|
956
|
+
}
|
|
735
957
|
|
|
736
958
|
// src/commands/doctor.ts
|
|
737
959
|
async function runDoctorCommand(ctx) {
|
|
@@ -868,9 +1090,9 @@ async function runDoneCommand(ctx, options) {
|
|
|
868
1090
|
notice: [
|
|
869
1091
|
`Work report recorded as event ${event.id}.`,
|
|
870
1092
|
"Notify your follow target if the report changes their next step.",
|
|
871
|
-
`Run \`codework poll --workspace=${workspace.value} --name=${agent.name}\` before the next substantial step.`
|
|
1093
|
+
`Run \`codework poll --workspace=${workspace.value} --name=${agent.name} --wait=30 --interval=2\` before the next substantial step.`
|
|
872
1094
|
],
|
|
873
|
-
recommendedCommand: `codework poll --workspace=${workspace.value} --name=${agent.name}`
|
|
1095
|
+
recommendedCommand: `codework poll --workspace=${workspace.value} --name=${agent.name} --wait=30 --interval=2`
|
|
874
1096
|
}),
|
|
875
1097
|
quietText: `event=${event.id} done
|
|
876
1098
|
`,
|
|
@@ -914,7 +1136,7 @@ async function runGuideCommand(ctx) {
|
|
|
914
1136
|
unreadEvents: unread,
|
|
915
1137
|
allEvents: events,
|
|
916
1138
|
notice: ["Full guide reprinted for the calling agent."],
|
|
917
|
-
recommendedCommand: `codework poll --workspace=${workspace.value} --name=${agent.name}`
|
|
1139
|
+
recommendedCommand: `codework poll --workspace=${workspace.value} --name=${agent.name} --wait=30 --interval=2`
|
|
918
1140
|
}),
|
|
919
1141
|
quietText: `workspace=${workspace.value} agent=${agent.name} guide
|
|
920
1142
|
`,
|
|
@@ -996,7 +1218,7 @@ async function runJoinCommand(ctx, options) {
|
|
|
996
1218
|
unreadEvents: unread,
|
|
997
1219
|
allEvents,
|
|
998
1220
|
notice,
|
|
999
|
-
recommendedCommand: `codework poll --workspace=${workspace.value} --name=${agent.name}`
|
|
1221
|
+
recommendedCommand: `codework poll --workspace=${workspace.value} --name=${agent.name} --wait=30 --interval=2`
|
|
1000
1222
|
}),
|
|
1001
1223
|
quietText: `workspace=${workspace.value} agent=${agent.name} joined
|
|
1002
1224
|
`,
|
|
@@ -1164,7 +1386,7 @@ async function runNewCommand(ctx, options) {
|
|
|
1164
1386
|
`Agent registered: ${agent.name}.`,
|
|
1165
1387
|
"This agent must keep using Codework commands for coordination."
|
|
1166
1388
|
],
|
|
1167
|
-
recommendedCommand: `codework poll --workspace=${workspace.value} --name=${agent.name}`
|
|
1389
|
+
recommendedCommand: `codework poll --workspace=${workspace.value} --name=${agent.name} --wait=30 --interval=2`
|
|
1168
1390
|
});
|
|
1169
1391
|
return {
|
|
1170
1392
|
text,
|
|
@@ -1185,50 +1407,147 @@ function requireNewOptions(options) {
|
|
|
1185
1407
|
}
|
|
1186
1408
|
|
|
1187
1409
|
// src/commands/poll.ts
|
|
1188
|
-
|
|
1410
|
+
import process5 from "process";
|
|
1411
|
+
var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1412
|
+
async function runPollCommand(ctx, options, commandKind = "poll") {
|
|
1189
1413
|
const workspace = slugifyIdentifier(ctx.workspace, "--workspace");
|
|
1190
1414
|
const name = required(ctx.name, "--name");
|
|
1191
|
-
const
|
|
1192
|
-
const
|
|
1415
|
+
const settings = normalizePollSettings(options);
|
|
1416
|
+
const canonicalCommand = buildCanonicalPollCommand({
|
|
1417
|
+
kind: commandKind,
|
|
1418
|
+
workspace: workspace.value,
|
|
1419
|
+
name,
|
|
1420
|
+
waitSeconds: settings.waitSeconds,
|
|
1421
|
+
intervalSeconds: settings.intervalSeconds
|
|
1422
|
+
});
|
|
1423
|
+
const nextPollCommand = buildCanonicalPollCommand({
|
|
1424
|
+
kind: "poll",
|
|
1425
|
+
workspace: workspace.value,
|
|
1426
|
+
name,
|
|
1427
|
+
waitSeconds: settings.waitSeconds,
|
|
1428
|
+
intervalSeconds: settings.intervalSeconds
|
|
1429
|
+
});
|
|
1193
1430
|
const paths = resolveCodeworkPaths({
|
|
1194
1431
|
cwd: ctx.cwd,
|
|
1195
1432
|
home: ctx.home,
|
|
1196
1433
|
workspace: workspace.value,
|
|
1197
1434
|
debug: ctx.debug
|
|
1198
1435
|
});
|
|
1199
|
-
|
|
1200
|
-
|
|
1436
|
+
const startedAt = Date.now();
|
|
1437
|
+
let readResult;
|
|
1438
|
+
while (true) {
|
|
1439
|
+
readResult = await readPollEvents(paths.workspaceDir, name, settings);
|
|
1440
|
+
if (readResult.actionableEvents.length > 0 || settings.once || settings.waitSeconds === 0) {
|
|
1441
|
+
break;
|
|
1442
|
+
}
|
|
1443
|
+
const remainingMs = startedAt + settings.waitSeconds * 1e3 - Date.now();
|
|
1444
|
+
if (remainingMs <= 0) {
|
|
1445
|
+
break;
|
|
1446
|
+
}
|
|
1447
|
+
await sleep2(Math.min(settings.intervalSeconds * 1e3, remainingMs));
|
|
1448
|
+
}
|
|
1449
|
+
const finalResult = await finalizePoll(paths.workspaceDir, name, settings, canonicalCommand);
|
|
1450
|
+
const pollResult = finalResult.actionableEvents.length > 0 ? "actionable-events-found" : "timeout-without-actionable-event";
|
|
1451
|
+
return {
|
|
1452
|
+
text: renderPollResult({
|
|
1453
|
+
title: commandKind === "wait" ? "# CODEWORK WAIT RESULT" : "# CODEWORK POLL RESULT",
|
|
1454
|
+
state: finalResult.state,
|
|
1455
|
+
agent: finalResult.agent,
|
|
1456
|
+
actionableEvents: finalResult.actionableEvents,
|
|
1457
|
+
nonActionableEvents: finalResult.nonActionableEvents,
|
|
1458
|
+
waitSeconds: settings.once ? 0 : settings.waitSeconds,
|
|
1459
|
+
intervalSeconds: settings.intervalSeconds,
|
|
1460
|
+
pollResult,
|
|
1461
|
+
canonicalCommand,
|
|
1462
|
+
nextPollCommand
|
|
1463
|
+
}),
|
|
1464
|
+
quietText: finalResult.actionableEvents.length === 0 ? `actionable=0 emptyPolls=${finalResult.agent.lastPollEmptyCount ?? 0}
|
|
1465
|
+
` : `actionable=${finalResult.actionableEvents.length}
|
|
1466
|
+
`,
|
|
1467
|
+
json: {
|
|
1468
|
+
ok: true,
|
|
1469
|
+
command: commandKind,
|
|
1470
|
+
workspace: finalResult.state.workspace,
|
|
1471
|
+
agent: finalResult.agent,
|
|
1472
|
+
pollResult,
|
|
1473
|
+
waitSeconds: settings.waitSeconds,
|
|
1474
|
+
intervalSeconds: settings.intervalSeconds,
|
|
1475
|
+
actionableEvents: finalResult.actionableEvents,
|
|
1476
|
+
nonActionableEvents: finalResult.nonActionableEvents,
|
|
1477
|
+
cursorEventId: finalResult.agent.cursorEventId,
|
|
1478
|
+
canonicalCommand
|
|
1479
|
+
}
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
function normalizePollSettings(options) {
|
|
1483
|
+
const waitSeconds = options.once ? 0 : parseBoundedSeconds({
|
|
1484
|
+
cliValue: options.wait,
|
|
1485
|
+
envValue: process5.env.CODEWORK_POLL_WAIT_SECONDS,
|
|
1486
|
+
optionName: "--wait",
|
|
1487
|
+
fallback: 30,
|
|
1488
|
+
min: 0,
|
|
1489
|
+
max: 120
|
|
1490
|
+
});
|
|
1491
|
+
const intervalSeconds = parseBoundedSeconds({
|
|
1492
|
+
cliValue: options.interval,
|
|
1493
|
+
envValue: process5.env.CODEWORK_POLL_INTERVAL_SECONDS,
|
|
1494
|
+
optionName: "--interval",
|
|
1495
|
+
fallback: 2,
|
|
1496
|
+
min: 1,
|
|
1497
|
+
max: 10
|
|
1498
|
+
});
|
|
1499
|
+
return {
|
|
1500
|
+
waitSeconds,
|
|
1501
|
+
intervalSeconds,
|
|
1502
|
+
once: Boolean(options.once),
|
|
1503
|
+
since: options.since === void 0 ? void 0 : parsePositiveInteger(options.since, "--since", 0),
|
|
1504
|
+
tail: options.tail === void 0 ? void 0 : parsePositiveInteger(options.tail, "--tail", 0)
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
async function readPollEvents(workspaceDir, name, settings) {
|
|
1508
|
+
return withWorkspaceLock(workspaceDir, async () => {
|
|
1509
|
+
const state = await requireState({ workspaceDir, cwd: "", root: "", home: "" });
|
|
1201
1510
|
const agent = findAgent(state, name);
|
|
1202
1511
|
if (!agent) {
|
|
1203
1512
|
throw new CodeworkError(2, `Agent is not registered in workspace: ${name}`);
|
|
1204
1513
|
}
|
|
1205
|
-
const events = await readEvents(
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
cursorEventId: agent.cursorEventId
|
|
1228
|
-
}
|
|
1229
|
-
};
|
|
1514
|
+
const events = await readEvents(workspaceDir);
|
|
1515
|
+
return buildPollReadResult(state, agent, events, settings);
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
async function finalizePoll(workspaceDir, name, settings, canonicalCommand) {
|
|
1519
|
+
return withWorkspaceLock(workspaceDir, async () => {
|
|
1520
|
+
const state = await requireState({ workspaceDir, cwd: "", root: "", home: "" });
|
|
1521
|
+
const agent = findAgent(state, name);
|
|
1522
|
+
if (!agent) {
|
|
1523
|
+
throw new CodeworkError(2, `Agent is not registered in workspace: ${name}`);
|
|
1524
|
+
}
|
|
1525
|
+
const events = await readEvents(workspaceDir);
|
|
1526
|
+
const result = buildPollReadResult(state, agent, events, settings);
|
|
1527
|
+
const maxDisplayedEventId = result.displayedEvents.reduce((max, event) => Math.max(max, event.id), 0);
|
|
1528
|
+
agent.cursorEventId = maxDisplayedEventId > 0 ? maxDisplayedEventId : latestEventId(state);
|
|
1529
|
+
agent.lastSeenAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1530
|
+
agent.lastPollAt = agent.lastSeenAt;
|
|
1531
|
+
agent.lastPollCommand = canonicalCommand;
|
|
1532
|
+
agent.lastPollEmptyCount = result.actionableEvents.length > 0 ? 0 : (agent.lastPollEmptyCount ?? 0) + 1;
|
|
1533
|
+
state.updatedAt = agent.lastSeenAt;
|
|
1534
|
+
await saveState(workspaceDir, state);
|
|
1535
|
+
return result;
|
|
1230
1536
|
});
|
|
1231
1537
|
}
|
|
1538
|
+
function buildPollReadResult(state, agent, allEvents, settings) {
|
|
1539
|
+
const unread = relevantUnreadEvents(state, allEvents, agent, settings.since);
|
|
1540
|
+
const displayedEvents = settings.tail === void 0 ? unread : unread.slice(0, settings.tail);
|
|
1541
|
+
const actionableEvents = displayedEvents.filter((event) => isActionableEvent(event, agent));
|
|
1542
|
+
const nonActionableEvents = displayedEvents.filter((event) => !isActionableEvent(event, agent));
|
|
1543
|
+
return {
|
|
1544
|
+
state,
|
|
1545
|
+
agent,
|
|
1546
|
+
displayedEvents,
|
|
1547
|
+
actionableEvents,
|
|
1548
|
+
nonActionableEvents
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1232
1551
|
|
|
1233
1552
|
// src/commands/say.ts
|
|
1234
1553
|
async function runSayCommand(ctx, options) {
|
|
@@ -1275,7 +1594,7 @@ async function runSayCommand(ctx, options) {
|
|
|
1275
1594
|
`Message recorded: kind=${kind}, to=${to}.`,
|
|
1276
1595
|
kind === "directive" ? "Directive messages must be treated as strong coordination instructions by recipients." : kind === "blocker" ? "Blocker messages are visible to all agents." : "Message is available through Codework poll/status/log."
|
|
1277
1596
|
],
|
|
1278
|
-
recommendedCommand: `codework poll --workspace=${workspace.value} --name=${agent.name}`
|
|
1597
|
+
recommendedCommand: `codework poll --workspace=${workspace.value} --name=${agent.name} --wait=30 --interval=2`
|
|
1279
1598
|
}),
|
|
1280
1599
|
quietText: `event=${event.id} kind=${kind} to=${to}
|
|
1281
1600
|
`,
|
|
@@ -1316,7 +1635,7 @@ async function runStatusCommand(ctx) {
|
|
|
1316
1635
|
unreadEvents: unread,
|
|
1317
1636
|
allEvents: events,
|
|
1318
1637
|
notice,
|
|
1319
|
-
recommendedCommand: agent ? `codework poll --workspace=${workspace.value} --name=${agent.name}` : `codework status --workspace=${workspace.value} --name=<agent>`,
|
|
1638
|
+
recommendedCommand: agent ? `codework poll --workspace=${workspace.value} --name=${agent.name} --wait=30 --interval=2` : `codework status --workspace=${workspace.value} --name=<agent>`,
|
|
1320
1639
|
statusMode: true
|
|
1321
1640
|
}),
|
|
1322
1641
|
quietText: `workspace=${workspace.value} agents=${Object.keys(state.agents).length}
|
|
@@ -1333,12 +1652,17 @@ async function runStatusCommand(ctx) {
|
|
|
1333
1652
|
};
|
|
1334
1653
|
}
|
|
1335
1654
|
|
|
1655
|
+
// src/commands/wait.ts
|
|
1656
|
+
async function runWaitCommand(ctx, options) {
|
|
1657
|
+
return runPollCommand(ctx, options, "wait");
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1336
1660
|
// src/cli.ts
|
|
1337
1661
|
var defaultIo = {
|
|
1338
|
-
stdout: (text) =>
|
|
1339
|
-
stderr: (text) =>
|
|
1662
|
+
stdout: (text) => process6.stdout.write(text),
|
|
1663
|
+
stderr: (text) => process6.stderr.write(text)
|
|
1340
1664
|
};
|
|
1341
|
-
async function main(argv =
|
|
1665
|
+
async function main(argv = process6.argv, io = defaultIo) {
|
|
1342
1666
|
const program = buildProgram(io);
|
|
1343
1667
|
const parsedArgv = [argv[0] ?? "node", argv[1] ?? "codework", ...preprocessArgv(argv.slice(2))];
|
|
1344
1668
|
try {
|
|
@@ -1392,9 +1716,12 @@ function buildProgram(io) {
|
|
|
1392
1716
|
await emitResult(io, contextFrom(this), await runStatusCommand(contextFrom(this)));
|
|
1393
1717
|
}
|
|
1394
1718
|
);
|
|
1395
|
-
addCommonOptions(program.command("poll").description("Read unread events for the calling agent.")).option("--since <eventId>", "Read events after this event id.").option("--tail <n>", "Limit unread events.").action(async function() {
|
|
1719
|
+
addCommonOptions(program.command("poll").description("Read unread events for the calling agent.")).option("--wait <seconds>", "Maximum seconds to wait for new actionable events.").option("--interval <seconds>", "Seconds between event log checks.").option("--since <eventId>", "Read events after this event id.").option("--tail <n>", "Limit unread events.").option("--once", "Read once without waiting.").action(async function() {
|
|
1396
1720
|
await emitResult(io, contextFrom(this), await runPollCommand(contextFrom(this), this.opts()));
|
|
1397
1721
|
});
|
|
1722
|
+
addCommonOptions(program.command("wait").description("Wait for actionable events for the calling agent.")).option("--wait <seconds>", "Maximum seconds to wait for new actionable events.").option("--interval <seconds>", "Seconds between event log checks.").option("--since <eventId>", "Read events after this event id.").option("--tail <n>", "Limit unread events.").option("--once", "Read once without waiting.").action(async function() {
|
|
1723
|
+
await emitResult(io, contextFrom(this), await runWaitCommand(contextFrom(this), this.opts()));
|
|
1724
|
+
});
|
|
1398
1725
|
addCommonOptions(program.command("say").description("Post a message event to the workspace.")).option("--message <text>", "Message body.").option("--to <agent|all>", "Recipient.", "all").option("--kind <note|directive|question|blocker>", "Message kind.", "note").action(async function() {
|
|
1399
1726
|
await emitResult(io, contextFrom(this), await runSayCommand(contextFrom(this), this.opts()));
|
|
1400
1727
|
});
|
|
@@ -1418,7 +1745,7 @@ function buildProgram(io) {
|
|
|
1418
1745
|
return program;
|
|
1419
1746
|
}
|
|
1420
1747
|
function addCommonOptions(command, withDefaults = false) {
|
|
1421
|
-
command.option("--workspace <id>", "Workspace id.").option("--name <agent>", "Calling agent name.").option("--format <format>", "text | json.", withDefaults ? "text" : void 0).option("--quiet", "Only print machine/minimal output.").option("--debug", "Print diagnostics to stderr.").option("--cwd <path>", "Repository/work root.", withDefaults ?
|
|
1748
|
+
command.option("--workspace <id>", "Workspace id.").option("--name <agent>", "Calling agent name.").option("--format <format>", "text | json.", withDefaults ? "text" : void 0).option("--quiet", "Only print machine/minimal output.").option("--debug", "Print diagnostics to stderr.").option("--cwd <path>", "Repository/work root.", withDefaults ? process6.cwd() : void 0).option("--home <path>", "Override Codework home.").option("--no-color", "Do not emit ANSI color.");
|
|
1422
1749
|
return command;
|
|
1423
1750
|
}
|
|
1424
1751
|
function contextFrom(command) {
|
|
@@ -1434,7 +1761,7 @@ function contextFrom(command) {
|
|
|
1434
1761
|
format: validateFormat(merged.format),
|
|
1435
1762
|
quiet: Boolean(merged.quiet),
|
|
1436
1763
|
debug: Boolean(merged.debug),
|
|
1437
|
-
cwd: merged.cwd ??
|
|
1764
|
+
cwd: merged.cwd ?? process6.cwd(),
|
|
1438
1765
|
home: merged.home
|
|
1439
1766
|
};
|
|
1440
1767
|
}
|
|
@@ -1487,10 +1814,10 @@ function requestedFormat(argv) {
|
|
|
1487
1814
|
}
|
|
1488
1815
|
if (isDirectRun()) {
|
|
1489
1816
|
const code = await main();
|
|
1490
|
-
|
|
1817
|
+
process6.exit(code);
|
|
1491
1818
|
}
|
|
1492
1819
|
function isDirectRun() {
|
|
1493
|
-
const argvPath =
|
|
1820
|
+
const argvPath = process6.argv[1];
|
|
1494
1821
|
if (!argvPath) {
|
|
1495
1822
|
return false;
|
|
1496
1823
|
}
|