@ccmsg/cli 0.2.13 → 0.3.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/package.json +2 -2
- package/src/cli.ts +96 -29
- package/src/daemon/registry.ts +41 -14
- package/src/daemon/supervise.ts +2 -1
- package/src/harness/index.ts +96 -0
- package/src/instance/config.ts +36 -0
- package/src/instance/instance.ts +28 -5
- package/src/instance/paths.ts +32 -6
- package/src/messaging/direct.ts +116 -0
- package/src/plugin/claude.ts +1 -72
- package/src/plugin/codex.ts +332 -0
- package/src/plugin/index.ts +7 -5
- package/src/plugin/install.ts +41 -161
- package/src/plugin/receipt.ts +202 -0
- package/src/plugin/skill.ts +84 -0
- package/src/sessions/harness.ts +166 -31
- package/src/sessions/registry.ts +84 -53
- package/src/sessions/search.ts +4 -4
- package/src/transcript/files.ts +103 -22
- package/src/transcript/fold.ts +72 -1
package/src/transcript/fold.ts
CHANGED
|
@@ -133,6 +133,11 @@ export class TranscriptFold {
|
|
|
133
133
|
return false;
|
|
134
134
|
}
|
|
135
135
|
if (!isRecord(row)) return false;
|
|
136
|
+
// A Codex rollout line settles one of these facts and none of the others,
|
|
137
|
+
// so it is folded on its own rather than run past readers of records it
|
|
138
|
+
// does not have (§3.8).
|
|
139
|
+
const rollout = rolloutRecord(row, str(row["type"]));
|
|
140
|
+
if (rollout !== undefined) return this.#foldRollout(rollout);
|
|
136
141
|
// Every value this fold derives, derived from the one parse (M5).
|
|
137
142
|
let changed = this.#foldApiError(row);
|
|
138
143
|
if (this.#foldAnswered(row)) changed = true;
|
|
@@ -223,6 +228,20 @@ export class TranscriptFold {
|
|
|
223
228
|
*
|
|
224
229
|
* A sidechain user row is a subagent being prompted by its parent, which is
|
|
225
230
|
* a session speaking to itself rather than a person speaking to it. */
|
|
231
|
+
/** What a Codex rollout says: when a person last spoke.
|
|
232
|
+
*
|
|
233
|
+
* The rest of what this fold holds — the api error, the model of the latest
|
|
234
|
+
* turn, todos, teammates, background work — are records Claude Code writes
|
|
235
|
+
* and a rollout does not, so they stay as they are for a Codex session
|
|
236
|
+
* rather than being guessed at from something that resembles them. */
|
|
237
|
+
#foldRollout(record: TranscriptRecord): boolean {
|
|
238
|
+
if (record.said_by !== "user" || record.said_at === undefined) return false;
|
|
239
|
+
if (record.text === undefined || !isHuman(record.text)) return false;
|
|
240
|
+
if ((this.#lastUserInputAt ?? 0) >= record.said_at) return false;
|
|
241
|
+
this.#lastUserInputAt = record.said_at;
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
|
|
226
245
|
#foldUserInput(row: Record<string, unknown>): boolean {
|
|
227
246
|
if (row["type"] !== "user" || row["isSidechain"] === true) return false;
|
|
228
247
|
if (row["isMeta"] === true || row["promptSource"] === "system") return false;
|
|
@@ -744,6 +763,8 @@ export function readRecord(line: string): TranscriptRecord | undefined {
|
|
|
744
763
|
}
|
|
745
764
|
if (!isRecord(row)) return undefined;
|
|
746
765
|
const type = str(row["type"]);
|
|
766
|
+
const rollout = rolloutRecord(row, type);
|
|
767
|
+
if (rollout !== undefined) return rollout;
|
|
747
768
|
const message = isRecord(row["message"]) ? row["message"] : undefined;
|
|
748
769
|
const model = message === undefined ? undefined : str(message["model"]);
|
|
749
770
|
return {
|
|
@@ -760,6 +781,52 @@ export function readRecord(line: string): TranscriptRecord | undefined {
|
|
|
760
781
|
};
|
|
761
782
|
}
|
|
762
783
|
|
|
784
|
+
/** One line of a Codex rollout, or nothing where the line is not one.
|
|
785
|
+
*
|
|
786
|
+
* A rollout says what kind of line it is in its own `type`, and the words it
|
|
787
|
+
* uses appear in no Claude Code transcript — so the two formats are told apart
|
|
788
|
+
* by the line rather than by anything the reader was told beforehand.
|
|
789
|
+
*
|
|
790
|
+
* What is read is what §5 asks a transcript for and a rollout answers: when a
|
|
791
|
+
* person last spoke, and where the session runs. The rest of the fold's facts —
|
|
792
|
+
* a session's todos, its teammates, the files it named — are Claude Code's own
|
|
793
|
+
* records, and a Codex session simply declares none of them.
|
|
794
|
+
*
|
|
795
|
+
* `developer` is not a person: Codex writes the instructions a turn runs under
|
|
796
|
+
* as messages of that role, so only `user` and `assistant` are read as somebody
|
|
797
|
+
* speaking. One of the `user` rows is not a person either — Codex opens a
|
|
798
|
+
* thread by stating the environment as `<environment_context>` — and it is the
|
|
799
|
+
* fold that turns that away, by the same rule that turns away every injected
|
|
800
|
+
* opening a Claude Code transcript carries: a row that opens with a tag is the
|
|
801
|
+
* harness talking. So the role decides who is read here, and what is read
|
|
802
|
+
* decides whether a person said it. */
|
|
803
|
+
function rolloutRecord(
|
|
804
|
+
row: Record<string, unknown>,
|
|
805
|
+
type: string | undefined,
|
|
806
|
+
): TranscriptRecord | undefined {
|
|
807
|
+
if (type === "session_meta") {
|
|
808
|
+
const payload = isRecord(row["payload"]) ? row["payload"] : undefined;
|
|
809
|
+
return {
|
|
810
|
+
sidechain: false,
|
|
811
|
+
...optional("said_at", instant(row["timestamp"])),
|
|
812
|
+
...optional("cwd", payload === undefined ? undefined : str(payload["cwd"])),
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
if (type !== "response_item") return undefined;
|
|
816
|
+
const payload = isRecord(row["payload"]) ? row["payload"] : undefined;
|
|
817
|
+
if (payload === undefined || str(payload["type"]) !== "message") return { sidechain: false };
|
|
818
|
+
const role = str(payload["role"]);
|
|
819
|
+
return {
|
|
820
|
+
sidechain: false,
|
|
821
|
+
...optional("said_at", instant(row["timestamp"])),
|
|
822
|
+
...optional<"said_by", "user" | "agent">(
|
|
823
|
+
"said_by",
|
|
824
|
+
role === "user" ? "user" : role === "assistant" ? "agent" : undefined,
|
|
825
|
+
),
|
|
826
|
+
...optional("text", blockText(payload["content"])),
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
|
|
763
830
|
/** The harness's record types, in the contract's two words. Its `assistant` is
|
|
764
831
|
* the contract's `agent`; every other type is a record neither side spoke. */
|
|
765
832
|
function saidBy(type: string | undefined): "user" | "agent" | undefined {
|
|
@@ -771,6 +838,8 @@ function optional<K extends string, V>(key: K, value: V | undefined): Record<K,
|
|
|
771
838
|
return value === undefined ? {} : { [key]: value };
|
|
772
839
|
}
|
|
773
840
|
|
|
841
|
+
const TEXT_BLOCK = new Set(["text", "input_text", "output_text"]);
|
|
842
|
+
|
|
774
843
|
/** The text a message states, whoever wrote it. A plain prompt is a string; a
|
|
775
844
|
* prompt with an attachment, and every row the harness writes, is a block
|
|
776
845
|
* array whose text blocks carry the words. An array holding only tool results
|
|
@@ -781,7 +850,9 @@ function blockText(content: unknown): string | undefined {
|
|
|
781
850
|
if (!Array.isArray(content)) return undefined;
|
|
782
851
|
const parts: string[] = [];
|
|
783
852
|
for (const block of content) {
|
|
784
|
-
|
|
853
|
+
// `text` is what Claude Code writes; `input_text` and `output_text` are
|
|
854
|
+
// what a Codex rollout writes for the same thing, one per direction.
|
|
855
|
+
if (!isRecord(block) || !TEXT_BLOCK.has(block["type"] as string)) continue;
|
|
785
856
|
const text = str(block["text"]);
|
|
786
857
|
if (text !== undefined) parts.push(text);
|
|
787
858
|
}
|