pi-cc-loop 0.7.2 → 0.7.3
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 +2 -0
- package/index.ts +97 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -69,6 +69,8 @@ ln -sfn "$PWD" ~/.pi/agent/extensions/pi-cc-loop
|
|
|
69
69
|
| `cron_list` | `CronList` | 列出任务和实际的下次运行时间(已含错峰) |
|
|
70
70
|
| `schedule_wakeup` | `ScheduleWakeup` | 自定步调:`delaySeconds`(限制在 60–3600)、`reason`(必填,显示在触发块表头)、`prompt`;`stop: true` 结束 |
|
|
71
71
|
|
|
72
|
+
四个工具都声明了 `outputSchema`:开了 codemode 时,脚本里 `tools.cron_create(...)`、`tools.cron_list()` 拿到的是结构化结果(`{ id, nextFireAt, … }`、`{ jobs: [...] }`),一个脚本里就能列任务、按条件批量取消,不用从文本里抠 id。没开 codemode 时没有影响。
|
|
73
|
+
|
|
72
74
|
## 渲染
|
|
73
75
|
|
|
74
76
|
定时触发只显示任务本身,给模型看的信封不上屏:
|
package/index.ts
CHANGED
|
@@ -143,6 +143,14 @@ function normalizePrompt(prompt: string): string {
|
|
|
143
143
|
return prompt.trim().replace(/^\/loop\s+/i, "").trim();
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
/** A stored job as a codemode script sees it: every field, minus those holding no value (the value is JSON). */
|
|
147
|
+
function jobValue(job: LoopJob): Record<string, string | number | boolean> {
|
|
148
|
+
return Object.fromEntries(Object.entries(storedJob(job)).filter(([, value]) => value !== undefined)) as Record<
|
|
149
|
+
string,
|
|
150
|
+
string | number | boolean
|
|
151
|
+
>;
|
|
152
|
+
}
|
|
153
|
+
|
|
146
154
|
/** The live scheduler, for tests. */
|
|
147
155
|
let activeScheduler: LoopScheduler | null = null;
|
|
148
156
|
|
|
@@ -681,6 +689,16 @@ export default function piCcLoopExtension(pi: ExtensionAPI): void {
|
|
|
681
689
|
),
|
|
682
690
|
}),
|
|
683
691
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
692
|
+
// What a codemode script gets: values, not the text (Pi sends the shape, not this).
|
|
693
|
+
outputSchema: Type.Object({
|
|
694
|
+
id: Type.String(),
|
|
695
|
+
cron: Type.String(),
|
|
696
|
+
recurring: Type.Boolean(),
|
|
697
|
+
durable: Type.Boolean(),
|
|
698
|
+
nextFireAt: Type.Optional(Type.Number()),
|
|
699
|
+
prompt: Type.String(),
|
|
700
|
+
runsHere: Type.Boolean(),
|
|
701
|
+
}),
|
|
684
702
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
685
703
|
lastCtx = ctx;
|
|
686
704
|
if (!parseCron(params.cron)) {
|
|
@@ -731,17 +749,21 @@ export default function piCcLoopExtension(pi: ExtensionAPI): void {
|
|
|
731
749
|
// Asked for in plain words, not through /loop: do it once now, as /loop would.
|
|
732
750
|
if (recurring && !currentRun.loopRequest && !currentRun.cronId && !currentRun.chainId) lines.push(RUN_NOW_HINT);
|
|
733
751
|
|
|
752
|
+
const value = {
|
|
753
|
+
id: job.id,
|
|
754
|
+
cron: job.cron ?? params.cron.trim(),
|
|
755
|
+
recurring: job.recurring,
|
|
756
|
+
durable: job.durable,
|
|
757
|
+
...(nextAt !== undefined ? { nextFireAt: nextAt } : {}),
|
|
758
|
+
prompt: job.prompt,
|
|
759
|
+
runsHere: placement.runsHere,
|
|
760
|
+
};
|
|
761
|
+
|
|
734
762
|
return {
|
|
735
763
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
recurring: job.recurring,
|
|
740
|
-
durable: job.durable,
|
|
741
|
-
nextFireAt: nextAt,
|
|
742
|
-
prompt: job.prompt,
|
|
743
|
-
runsHere: placement.runsHere,
|
|
744
|
-
},
|
|
764
|
+
// Shared with codemode scripts; no key holds undefined, so it is JSON.
|
|
765
|
+
details: value,
|
|
766
|
+
structuredContent: value,
|
|
745
767
|
};
|
|
746
768
|
},
|
|
747
769
|
});
|
|
@@ -755,6 +777,7 @@ export default function piCcLoopExtension(pi: ExtensionAPI): void {
|
|
|
755
777
|
id: Type.String({ description: "The job id returned by cron_create, or the id of a self-paced loop." }),
|
|
756
778
|
}),
|
|
757
779
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
780
|
+
outputSchema: Type.Object({ cancelled: Type.Boolean(), id: Type.String() }),
|
|
758
781
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
759
782
|
lastCtx = ctx;
|
|
760
783
|
const job = scheduler.get(params.id);
|
|
@@ -763,14 +786,18 @@ export default function piCcLoopExtension(pi: ExtensionAPI): void {
|
|
|
763
786
|
if (job.kind === "wakeup") retireChain(job, "cancelled with cron_delete");
|
|
764
787
|
else removeJob(job);
|
|
765
788
|
} else if (!durable?.remove(params.id)) {
|
|
789
|
+
const value = { cancelled: false, id: params.id };
|
|
766
790
|
return {
|
|
767
791
|
content: [{ type: "text" as const, text: `No scheduled job with id ${params.id}.` }],
|
|
768
|
-
details:
|
|
792
|
+
details: value,
|
|
793
|
+
structuredContent: value,
|
|
769
794
|
};
|
|
770
795
|
}
|
|
796
|
+
const value = { cancelled: true, id: params.id };
|
|
771
797
|
return {
|
|
772
798
|
content: [{ type: "text" as const, text: `Cancelled job ${params.id}.` }],
|
|
773
|
-
details:
|
|
799
|
+
details: value,
|
|
800
|
+
structuredContent: value,
|
|
774
801
|
};
|
|
775
802
|
},
|
|
776
803
|
});
|
|
@@ -782,13 +809,40 @@ export default function piCcLoopExtension(pi: ExtensionAPI): void {
|
|
|
782
809
|
promptSnippet: CRON_LIST_SNIPPET,
|
|
783
810
|
parameters: Type.Object({}),
|
|
784
811
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
812
|
+
outputSchema: Type.Object({
|
|
813
|
+
jobs: Type.Array(
|
|
814
|
+
Type.Object({
|
|
815
|
+
id: Type.String(),
|
|
816
|
+
kind: Type.Union([Type.Literal("cron"), Type.Literal("wakeup")]),
|
|
817
|
+
cron: Type.Optional(Type.String()),
|
|
818
|
+
prompt: Type.String(),
|
|
819
|
+
recurring: Type.Boolean(),
|
|
820
|
+
createdAt: Type.Number(),
|
|
821
|
+
nextFireAt: Type.Optional(Type.Number()),
|
|
822
|
+
scheduledFor: Type.Optional(Type.Number()),
|
|
823
|
+
chainStartedAt: Type.Optional(Type.Number()),
|
|
824
|
+
keepaliveUsed: Type.Optional(Type.Boolean()),
|
|
825
|
+
fireCount: Type.Optional(Type.Number()),
|
|
826
|
+
tickCount: Type.Optional(Type.Number()),
|
|
827
|
+
lastFiredAt: Type.Optional(Type.Number()),
|
|
828
|
+
maxFires: Type.Optional(Type.Number()),
|
|
829
|
+
durable: Type.Boolean(),
|
|
830
|
+
createdInProject: Type.Optional(Type.String()),
|
|
831
|
+
label: Type.Optional(Type.String()),
|
|
832
|
+
reason: Type.Optional(Type.String()),
|
|
833
|
+
source: Type.String(),
|
|
834
|
+
}),
|
|
835
|
+
),
|
|
836
|
+
}),
|
|
785
837
|
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
786
838
|
lastCtx = ctx;
|
|
787
839
|
const jobs = listJobs();
|
|
840
|
+
const values = jobs.map(jobValue);
|
|
788
841
|
if (!jobs.length) {
|
|
789
842
|
return {
|
|
790
843
|
content: [{ type: "text" as const, text: "No scheduled jobs." }],
|
|
791
|
-
details: { jobs:
|
|
844
|
+
details: { jobs: values },
|
|
845
|
+
structuredContent: { jobs: values },
|
|
792
846
|
};
|
|
793
847
|
}
|
|
794
848
|
const text = jobs
|
|
@@ -802,7 +856,8 @@ export default function piCcLoopExtension(pi: ExtensionAPI): void {
|
|
|
802
856
|
.join("\n");
|
|
803
857
|
return {
|
|
804
858
|
content: [{ type: "text" as const, text }],
|
|
805
|
-
details: { jobs:
|
|
859
|
+
details: { jobs: values },
|
|
860
|
+
structuredContent: { jobs: values },
|
|
806
861
|
};
|
|
807
862
|
},
|
|
808
863
|
});
|
|
@@ -836,6 +891,17 @@ export default function piCcLoopExtension(pi: ExtensionAPI): void {
|
|
|
836
891
|
),
|
|
837
892
|
}),
|
|
838
893
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
894
|
+
// Every return is one of these shapes: a stop, a suppression, an age-out, or a schedule.
|
|
895
|
+
outputSchema: Type.Object({
|
|
896
|
+
stopped: Type.Boolean(),
|
|
897
|
+
cancelledWakeups: Type.Optional(Type.Number()),
|
|
898
|
+
agedOut: Type.Optional(Type.Boolean()),
|
|
899
|
+
suppressed: Type.Optional(Type.String()),
|
|
900
|
+
id: Type.Optional(Type.String()),
|
|
901
|
+
scheduledFor: Type.Optional(Type.Number()),
|
|
902
|
+
clampedDelaySeconds: Type.Optional(Type.Number()),
|
|
903
|
+
wasClamped: Type.Optional(Type.Boolean()),
|
|
904
|
+
}),
|
|
839
905
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
840
906
|
lastCtx = ctx;
|
|
841
907
|
|
|
@@ -845,6 +911,7 @@ export default function piCcLoopExtension(pi: ExtensionAPI): void {
|
|
|
845
911
|
const named = params.prompt ? scheduler.findChain(normalizePrompt(params.prompt)) : undefined;
|
|
846
912
|
const chains = current ? [current] : named ? [named] : scheduler.byKind("wakeup");
|
|
847
913
|
for (const chain of chains) retireChain(chain, "stopped by the agent");
|
|
914
|
+
const value = { stopped: true, cancelledWakeups: chains.length };
|
|
848
915
|
return {
|
|
849
916
|
content: [
|
|
850
917
|
{
|
|
@@ -856,7 +923,8 @@ export default function piCcLoopExtension(pi: ExtensionAPI): void {
|
|
|
856
923
|
"If you are running a fixed-interval /loop (a recurring cron), it is NOT stopped by this call \u2014 cancel it with cron_delete.",
|
|
857
924
|
},
|
|
858
925
|
],
|
|
859
|
-
details:
|
|
926
|
+
details: value,
|
|
927
|
+
structuredContent: value,
|
|
860
928
|
};
|
|
861
929
|
}
|
|
862
930
|
|
|
@@ -876,6 +944,7 @@ export default function piCcLoopExtension(pi: ExtensionAPI): void {
|
|
|
876
944
|
// Returning it as a result (instead of throwing) keeps the transcript
|
|
877
945
|
// calm and tells the model nothing is missing.
|
|
878
946
|
const cronJob = scheduler.get(currentRun.cronId);
|
|
947
|
+
const value = { suppressed: "fixed-interval", id: currentRun.cronId, stopped: false };
|
|
879
948
|
return {
|
|
880
949
|
content: [
|
|
881
950
|
{
|
|
@@ -886,7 +955,8 @@ export default function piCcLoopExtension(pi: ExtensionAPI): void {
|
|
|
886
955
|
`Nothing was scheduled. To end the loop, call cron_delete with id ${currentRun.cronId}.`,
|
|
887
956
|
},
|
|
888
957
|
],
|
|
889
|
-
details:
|
|
958
|
+
details: value,
|
|
959
|
+
structuredContent: value,
|
|
890
960
|
};
|
|
891
961
|
}
|
|
892
962
|
|
|
@@ -904,6 +974,7 @@ export default function piCcLoopExtension(pi: ExtensionAPI): void {
|
|
|
904
974
|
const limit = maxAgeMs();
|
|
905
975
|
if (limit > 0 && now - (chain.chainStartedAt ?? chain.createdAt) >= limit) {
|
|
906
976
|
retireChain(chain, `reached its ${maxAgeDays()}-day limit`);
|
|
977
|
+
const value = { stopped: true, agedOut: true, id: chain.id, cancelledWakeups: 1 };
|
|
907
978
|
return {
|
|
908
979
|
content: [
|
|
909
980
|
{
|
|
@@ -913,7 +984,8 @@ export default function piCcLoopExtension(pi: ExtensionAPI): void {
|
|
|
913
984
|
"do not re-issue. Tell the user they can start a new /loop.",
|
|
914
985
|
},
|
|
915
986
|
],
|
|
916
|
-
details:
|
|
987
|
+
details: value,
|
|
988
|
+
structuredContent: value,
|
|
917
989
|
};
|
|
918
990
|
}
|
|
919
991
|
} else {
|
|
@@ -950,6 +1022,13 @@ export default function piCcLoopExtension(pi: ExtensionAPI): void {
|
|
|
950
1022
|
|
|
951
1023
|
const note = wasClamped ? ` (clamped to ${delay}s from your requested value)` : "";
|
|
952
1024
|
const clock = new Date(scheduledFor).toTimeString().slice(0, 8);
|
|
1025
|
+
const value = {
|
|
1026
|
+
id: chain.id,
|
|
1027
|
+
scheduledFor,
|
|
1028
|
+
clampedDelaySeconds: delay,
|
|
1029
|
+
wasClamped,
|
|
1030
|
+
stopped: false,
|
|
1031
|
+
};
|
|
953
1032
|
return {
|
|
954
1033
|
content: [
|
|
955
1034
|
{
|
|
@@ -959,13 +1038,8 @@ export default function piCcLoopExtension(pi: ExtensionAPI): void {
|
|
|
959
1038
|
"This turn ends when this call returns \u2014 you are re-invoked when the wakeup fires or a monitor event arrives.",
|
|
960
1039
|
},
|
|
961
1040
|
],
|
|
962
|
-
details:
|
|
963
|
-
|
|
964
|
-
scheduledFor,
|
|
965
|
-
clampedDelaySeconds: delay,
|
|
966
|
-
wasClamped,
|
|
967
|
-
stopped: false,
|
|
968
|
-
},
|
|
1041
|
+
details: value,
|
|
1042
|
+
structuredContent: value,
|
|
969
1043
|
// Claude Code ends the turn when ScheduleWakeup returns (on models
|
|
970
1044
|
// that support it); the guidelines ask for the update first. Pi only
|
|
971
1045
|
// ends the turn when every tool in the batch agrees.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-cc-loop",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
4
4
|
"description": "Claude Code compatible /loop for the Pi coding agent: cron-scheduled fixed intervals, self-paced chains, and the same dispatch semantics as Claude Code.",
|
|
5
5
|
"author": "tarnish233",
|
|
6
6
|
"type": "module",
|