@moxt-ai/cli 0.3.3-moxt-run.1 → 0.3.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 +52 -32
- package/dist/index.js +1067 -2251
- package/dist/index.js.map +1 -1
- package/package.json +4 -6
package/dist/index.js
CHANGED
|
@@ -149,7 +149,7 @@ function getApiKeyOrUndefined() {
|
|
|
149
149
|
|
|
150
150
|
// src/utils/http.ts
|
|
151
151
|
function getUserAgent() {
|
|
152
|
-
return `moxt-cli/${"0.3.3
|
|
152
|
+
return `moxt-cli/${"0.3.3"} (${platform()}/${arch()}) node/${process.versions.node}`;
|
|
153
153
|
}
|
|
154
154
|
async function fetchApi(method, path2, body) {
|
|
155
155
|
const apiKey = getApiKey();
|
|
@@ -189,6 +189,9 @@ async function httpPut(path2, body) {
|
|
|
189
189
|
async function httpPost(path2, body) {
|
|
190
190
|
return request("POST", path2, body);
|
|
191
191
|
}
|
|
192
|
+
async function httpPatch(path2, body) {
|
|
193
|
+
return request("PATCH", path2, body);
|
|
194
|
+
}
|
|
192
195
|
async function httpDelete(path2, body) {
|
|
193
196
|
return request("DELETE", path2, body);
|
|
194
197
|
}
|
|
@@ -744,2334 +747,1147 @@ function registerMiniappCommand(program2) {
|
|
|
744
747
|
});
|
|
745
748
|
}
|
|
746
749
|
|
|
747
|
-
// src/
|
|
748
|
-
import
|
|
749
|
-
import
|
|
750
|
-
import
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
".DS_Store",
|
|
762
|
-
RUNLOG_DIRECTORY_MARKER,
|
|
763
|
-
"events",
|
|
764
|
-
"events.jsonl",
|
|
765
|
-
"run.json",
|
|
766
|
-
"state.json",
|
|
767
|
-
"transcript",
|
|
768
|
-
"transcript.jsonl",
|
|
769
|
-
"upload.json"
|
|
770
|
-
]);
|
|
771
|
-
var RUNLOG_METADATA_SCHEMAS = {
|
|
772
|
-
"run.json": "runlog.capture.v1",
|
|
773
|
-
"state.json": "runlog.state.v1",
|
|
774
|
-
"upload.json": "runlog.upload.v1"
|
|
775
|
-
};
|
|
776
|
-
var RUNLOG_METADATA_FILE_NAMES = Object.keys(RUNLOG_METADATA_SCHEMAS);
|
|
777
|
-
var CaptureOutputPrepareError = class extends Error {
|
|
778
|
-
path;
|
|
779
|
-
constructor(path2, cause) {
|
|
780
|
-
super(errorMessage(cause));
|
|
781
|
-
this.name = "CaptureOutputPrepareError";
|
|
782
|
-
this.path = path2;
|
|
783
|
-
this.cause = cause;
|
|
784
|
-
}
|
|
785
|
-
};
|
|
786
|
-
function defaultCaptureOutputRoot(home = homedir(), env = process.env, nodePlatform = process.platform) {
|
|
787
|
-
const pathJoin = nodePlatform === "win32" ? win32.join : posix.join;
|
|
788
|
-
if (nodePlatform === "darwin") {
|
|
789
|
-
return pathJoin(home, "Library", "Application Support", "moxt", "runs");
|
|
790
|
-
}
|
|
791
|
-
if (nodePlatform === "win32") {
|
|
792
|
-
return pathJoin(env.LOCALAPPDATA ?? pathJoin(home, "AppData", "Local"), "Moxt", "runs");
|
|
750
|
+
// src/telemetry/config.ts
|
|
751
|
+
import * as fs2 from "fs";
|
|
752
|
+
import * as os from "os";
|
|
753
|
+
import * as path from "path";
|
|
754
|
+
function getConfigPath() {
|
|
755
|
+
return path.join(os.homedir(), ".moxt", "config.json");
|
|
756
|
+
}
|
|
757
|
+
function readTelemetryConfig() {
|
|
758
|
+
try {
|
|
759
|
+
const raw = fs2.readFileSync(getConfigPath(), "utf8");
|
|
760
|
+
const parsed = JSON.parse(raw);
|
|
761
|
+
return parsed.telemetry ?? {};
|
|
762
|
+
} catch {
|
|
763
|
+
return {};
|
|
793
764
|
}
|
|
794
|
-
return pathJoin(env.XDG_DATA_HOME ?? pathJoin(home, ".local", "share"), "moxt", "runs");
|
|
795
765
|
}
|
|
796
|
-
function
|
|
797
|
-
const
|
|
798
|
-
|
|
799
|
-
|
|
766
|
+
function writeTelemetryConfig(update) {
|
|
767
|
+
const configPath = getConfigPath();
|
|
768
|
+
let existing = {};
|
|
769
|
+
try {
|
|
770
|
+
existing = JSON.parse(fs2.readFileSync(configPath, "utf8"));
|
|
771
|
+
} catch {
|
|
772
|
+
existing = {};
|
|
800
773
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
774
|
+
const currentTelemetry = existing.telemetry ?? {};
|
|
775
|
+
const next = { ...existing, telemetry: { ...currentTelemetry, ...update } };
|
|
776
|
+
fs2.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
777
|
+
fs2.writeFileSync(configPath, JSON.stringify(next, null, 2));
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// src/telemetry/opt-out.ts
|
|
781
|
+
function isCiEnvironment() {
|
|
782
|
+
return process.env.CI === "true" || process.env.CI === "1";
|
|
805
783
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
await prepareOutputPath(outputDir, () => prepareCaptureDirectory(outputDir));
|
|
784
|
+
function isTelemetryDisabled() {
|
|
785
|
+
if (process.env.MOXT_TELEMETRY_DISABLED === "1" || process.env.MOXT_TELEMETRY_DISABLED === "true") {
|
|
786
|
+
return true;
|
|
810
787
|
}
|
|
811
|
-
if (
|
|
812
|
-
|
|
813
|
-
await prepareOutputPath(runDir, () => prepareCaptureDirectory(runDir));
|
|
788
|
+
if (process.env.DO_NOT_TRACK === "1" || process.env.DO_NOT_TRACK === "true") {
|
|
789
|
+
return true;
|
|
814
790
|
}
|
|
791
|
+
return readTelemetryConfig().disabled === true;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// src/commands/telemetry.ts
|
|
795
|
+
function registerTelemetryCommand(program2) {
|
|
796
|
+
const telemetry = program2.command("telemetry").description("Manage Moxt CLI telemetry");
|
|
797
|
+
telemetry.command("status").description("Show telemetry status").action(() => {
|
|
798
|
+
const cfg = readTelemetryConfig();
|
|
799
|
+
const envOverride = process.env.MOXT_TELEMETRY_DISABLED === "1" || process.env.MOXT_TELEMETRY_DISABLED === "true" || process.env.DO_NOT_TRACK === "1" || process.env.DO_NOT_TRACK === "true";
|
|
800
|
+
const state = isTelemetryDisabled() ? "disabled" : "enabled";
|
|
801
|
+
print(`telemetry: ${state}`);
|
|
802
|
+
if (envOverride) {
|
|
803
|
+
print("(disabled via environment variable)");
|
|
804
|
+
} else if (cfg.disabled) {
|
|
805
|
+
print("(disabled via config file)");
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
telemetry.command("enable").description("Enable telemetry").action(() => {
|
|
809
|
+
writeTelemetryConfig({ disabled: false });
|
|
810
|
+
print("telemetry: enabled");
|
|
811
|
+
});
|
|
812
|
+
telemetry.command("disable").description("Disable telemetry").action(() => {
|
|
813
|
+
writeTelemetryConfig({ disabled: true });
|
|
814
|
+
print("telemetry: disabled");
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// src/commands/whoami.ts
|
|
819
|
+
function registerWhoamiCommand(program2) {
|
|
820
|
+
program2.command("whoami").description("Show current user information").action(async () => {
|
|
821
|
+
startSpinner("Fetching user info...");
|
|
822
|
+
const response = await httpGet("/users/whoami");
|
|
823
|
+
if (response.ok) {
|
|
824
|
+
stopSpinner(true, "Done");
|
|
825
|
+
print(`${colors.bold}Nickname${colors.reset}: ${response.data.nickname}`);
|
|
826
|
+
print(`${colors.bold}Email${colors.reset}: ${response.data.email}`);
|
|
827
|
+
} else {
|
|
828
|
+
stopSpinner(false, "Failed");
|
|
829
|
+
printError(`Error [${response.status}]: ${JSON.stringify(response.data)}`);
|
|
830
|
+
process.exit(1);
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// src/commands/workspace.ts
|
|
836
|
+
var emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
|
837
|
+
function isValidEmail(email) {
|
|
838
|
+
return emailRegex.test(email);
|
|
839
|
+
}
|
|
840
|
+
function registerWorkspaceCommand(program2) {
|
|
841
|
+
const workspace = program2.command("workspace").description("Workspace management");
|
|
842
|
+
workspace.command("list").description("List all workspaces you belong to").action(async () => {
|
|
843
|
+
startSpinner("Fetching workspaces...");
|
|
844
|
+
const response = await httpGet("/workspaces");
|
|
845
|
+
if (response.ok) {
|
|
846
|
+
const { workspaces } = response.data;
|
|
847
|
+
if (workspaces.length === 0) {
|
|
848
|
+
stopSpinner(true, "No workspaces found.");
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
stopSpinner(true, `Found ${workspaces.length} workspace(s)`);
|
|
852
|
+
for (const ws of workspaces) {
|
|
853
|
+
print(`${colors.bold}${ws.workspaceId}${colors.reset} ${ws.name}`);
|
|
854
|
+
}
|
|
855
|
+
} else {
|
|
856
|
+
stopSpinner(false, "Failed");
|
|
857
|
+
printError(`Error [${response.status}]: ${JSON.stringify(response.data)}`);
|
|
858
|
+
process.exit(1);
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
const members = program2.command("members").description("Manage workspace members");
|
|
862
|
+
members.command("list").description("List all members in a workspace").requiredOption("-w, --workspace <workspaceId>", "Workspace ID").action(async (options) => {
|
|
863
|
+
const { workspace: workspaceId } = options;
|
|
864
|
+
startSpinner("Fetching workspace members...");
|
|
865
|
+
const response = await httpGet(`/workspaces/${workspaceId}/members`);
|
|
866
|
+
if (!response.ok) {
|
|
867
|
+
stopSpinner(false, "Failed to fetch members");
|
|
868
|
+
printError(`Error [${response.status}]: ${JSON.stringify(response.data)}`);
|
|
869
|
+
process.exit(1);
|
|
870
|
+
}
|
|
871
|
+
const { members: members2 } = response.data;
|
|
872
|
+
if (members2.length === 0) {
|
|
873
|
+
stopSpinner(true, "No members found.");
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
stopSpinner(true, `Found ${members2.length} member(s)`);
|
|
877
|
+
for (const member of members2) {
|
|
878
|
+
const name = member.displayName || "-";
|
|
879
|
+
print(`${colors.bold}${member.email}${colors.reset} ${name} ${member.role}`);
|
|
880
|
+
}
|
|
881
|
+
});
|
|
882
|
+
members.command("remove").description("Remove members from a workspace").requiredOption("-w, --workspace <workspaceId>", "Workspace ID").argument("<emails>", "Emails to remove (comma-separated)").action(async (emailsArg, options) => {
|
|
883
|
+
const { workspace: workspaceId } = options;
|
|
884
|
+
const emails = emailsArg.split(",").map((e) => e.trim()).filter(Boolean);
|
|
885
|
+
if (emails.length === 0) {
|
|
886
|
+
printError("No valid emails provided");
|
|
887
|
+
process.exit(1);
|
|
888
|
+
}
|
|
889
|
+
const invalidEmails = emails.filter((e) => !isValidEmail(e));
|
|
890
|
+
if (invalidEmails.length > 0) {
|
|
891
|
+
for (const email of invalidEmails) {
|
|
892
|
+
printError(`Invalid email format: ${email}`);
|
|
893
|
+
}
|
|
894
|
+
process.exit(1);
|
|
895
|
+
}
|
|
896
|
+
startSpinner("Fetching workspace members...");
|
|
897
|
+
const membersResponse = await httpGet(`/workspaces/${workspaceId}/members`);
|
|
898
|
+
if (!membersResponse.ok) {
|
|
899
|
+
stopSpinner(false, "Failed to fetch members");
|
|
900
|
+
printError(`Error [${membersResponse.status}]: ${JSON.stringify(membersResponse.data)}`);
|
|
901
|
+
process.exit(1);
|
|
902
|
+
}
|
|
903
|
+
stopSpinner(true, "Members fetched");
|
|
904
|
+
const { members: members2 } = membersResponse.data;
|
|
905
|
+
const emailToMember = new Map(members2.map((m) => [m.email.toLowerCase(), m]));
|
|
906
|
+
for (const email of emails) {
|
|
907
|
+
const member = emailToMember.get(email.toLowerCase());
|
|
908
|
+
if (!member) {
|
|
909
|
+
print(`${colors.red}\u2717${colors.reset} ${email} - not found in workspace`);
|
|
910
|
+
continue;
|
|
911
|
+
}
|
|
912
|
+
const deleteResponse = await httpDelete(`/workspaces/${workspaceId}/members/${member.userId}`);
|
|
913
|
+
if (deleteResponse.ok) {
|
|
914
|
+
print(`${colors.green}\u2713${colors.reset} ${email} - removed`);
|
|
915
|
+
} else {
|
|
916
|
+
print(`${colors.red}\u2717${colors.reset} ${email} - failed: ${JSON.stringify(deleteResponse.data)}`);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
const space = program2.command("space").description("Manage spaces");
|
|
921
|
+
space.command("list").description("List accessible spaces in a workspace").requiredOption("-w, --workspace <workspaceId>", "Workspace ID").action(async (options) => {
|
|
922
|
+
const { workspace: workspaceId } = options;
|
|
923
|
+
startSpinner("Fetching spaces...");
|
|
924
|
+
const response = await httpGet(`/workspaces/${workspaceId}/spaces`);
|
|
925
|
+
if (!response.ok) {
|
|
926
|
+
stopSpinner(false, "Failed to fetch spaces");
|
|
927
|
+
printError(`Error [${response.status}]: ${JSON.stringify(response.data)}`);
|
|
928
|
+
process.exit(1);
|
|
929
|
+
}
|
|
930
|
+
const { spaces } = response.data;
|
|
931
|
+
if (spaces.length === 0) {
|
|
932
|
+
stopSpinner(true, "No spaces found.");
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
stopSpinner(true, `Found ${spaces.length} space(s)`);
|
|
936
|
+
print(
|
|
937
|
+
`${colors.bold}TYPE${colors.reset} ${colors.bold}TEAM_SPACE_ID${colors.reset} ${colors.bold}TEAMMATE_ID${colors.reset} ${colors.bold}NAME${colors.reset}`
|
|
938
|
+
);
|
|
939
|
+
for (const s of spaces) {
|
|
940
|
+
const teamSpaceId = s.teamSpaceId ?? "";
|
|
941
|
+
const teammateId = s.agentId != null ? String(s.agentId) : "";
|
|
942
|
+
print(`${s.type} ${teamSpaceId} ${teammateId} ${s.name}`);
|
|
943
|
+
}
|
|
944
|
+
});
|
|
815
945
|
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
946
|
+
|
|
947
|
+
// src/commands/workflow.ts
|
|
948
|
+
import * as fs3 from "fs";
|
|
949
|
+
import { TextDecoder } from "util";
|
|
950
|
+
var MAX_DESC_LENGTH = 5e3;
|
|
951
|
+
var MAX_COMMENT_CONTENT_LENGTH = 5e3;
|
|
952
|
+
var MAX_COMMENT_CONTENT_BYTES = MAX_COMMENT_CONTENT_LENGTH * 4;
|
|
953
|
+
var WorkflowOptionsError = class extends Error {
|
|
954
|
+
constructor(message) {
|
|
955
|
+
super(message);
|
|
956
|
+
this.name = "WorkflowOptionsError";
|
|
821
957
|
}
|
|
822
|
-
|
|
823
|
-
|
|
958
|
+
};
|
|
959
|
+
function runOrExit3(fn) {
|
|
960
|
+
try {
|
|
961
|
+
return fn();
|
|
962
|
+
} catch (err) {
|
|
963
|
+
if (err instanceof WorkflowOptionsError || err instanceof SpaceOptionsError || err instanceof SearchOptionsError) {
|
|
964
|
+
printError(err.message);
|
|
965
|
+
process.exit(1);
|
|
966
|
+
}
|
|
967
|
+
throw err;
|
|
824
968
|
}
|
|
825
|
-
return [...dirs];
|
|
826
969
|
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
captureOutputDirectories(env, runId).map(async (outputDir) => {
|
|
830
|
-
await writeCaptureUploadFile(outputDir, runId);
|
|
831
|
-
await writeCaptureStateFile(outputDir, runId, "open", capturedAt);
|
|
832
|
-
})
|
|
833
|
-
);
|
|
970
|
+
function addWorkspaceOption(command) {
|
|
971
|
+
return command.requiredOption("-w, --workspace <workspaceId>", "Workspace ID");
|
|
834
972
|
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
captureOutputDirectories(env, runId).map(async (outputDir) => {
|
|
838
|
-
await writeCaptureRunFile(outputDir, payload, runId, files);
|
|
839
|
-
await writeCaptureUploadFile(outputDir, runId);
|
|
840
|
-
await writeCaptureStateFile(outputDir, runId, "closed", capturedAt);
|
|
841
|
-
})
|
|
842
|
-
);
|
|
973
|
+
function addSpaceOptions2(command) {
|
|
974
|
+
return addWorkspaceOption(command).option("-s, --space <name>", "Space name (personal or teamspace name)").option("--personal", "Use personal space (default)").option("--team-space-id <teamSpaceId>", "Team space ID").option("--teammate-id <teammateId>", "Teammate ID (teammate space)");
|
|
843
975
|
}
|
|
844
|
-
function
|
|
845
|
-
|
|
846
|
-
const suffix = randomUUID().replaceAll("-", "").slice(0, 8);
|
|
847
|
-
return agent === void 0 ? `${timestamp}-${suffix}` : `${timestamp}-${agent}-${suffix}`;
|
|
848
|
-
}
|
|
849
|
-
async function writeCaptureDirectoryPayload(payload, env, runId, capturedAt) {
|
|
850
|
-
const hasTranscript = payload.transcript_redacted !== void 0;
|
|
851
|
-
const transcript = hasTranscript ? normalizeJsonl(payload.transcript_redacted ?? "") : "";
|
|
852
|
-
const transcriptChunks = hasTranscript ? chunkJsonl(transcript, "transcript", "transcript") : [];
|
|
853
|
-
const eventChunks = hasTranscript ? chunkJsonl(serializeEventsFromTranscript(transcript, 1, payload.run.agent).text, "events", "events") : [];
|
|
854
|
-
await Promise.all(
|
|
855
|
-
captureOutputDirectories(env, runId).map(async (outputDir) => {
|
|
856
|
-
await prepareCaptureDirectory(outputDir);
|
|
857
|
-
if (hasTranscript) {
|
|
858
|
-
await writeChunks(outputDir, eventChunks);
|
|
859
|
-
await writeChunks(outputDir, transcriptChunks);
|
|
860
|
-
}
|
|
861
|
-
await writeCaptureRunFile(outputDir, payload, runId, {
|
|
862
|
-
events: eventChunks.map(withoutContent),
|
|
863
|
-
transcript: transcriptChunks.map(withoutContent)
|
|
864
|
-
});
|
|
865
|
-
await writeCaptureUploadFile(outputDir, runId);
|
|
866
|
-
await writeCaptureStateFile(outputDir, runId, "closed", capturedAt);
|
|
867
|
-
})
|
|
868
|
-
);
|
|
976
|
+
function addAssigneeOptions(command) {
|
|
977
|
+
return command.option("--human-id <humanId>", "Human assignee ID").option("--assistant-id <assistantId>", "AI assistant assignee ID").option("--teammate-id <teammateId>", "AI teammate assignee ID");
|
|
869
978
|
}
|
|
870
|
-
function
|
|
871
|
-
|
|
872
|
-
return {
|
|
873
|
-
text: serializeEvents(events),
|
|
874
|
-
nextSeq: startSeq + events.length
|
|
875
|
-
};
|
|
979
|
+
function addPreferredAssigneeOptions(command) {
|
|
980
|
+
return command.option("--preferred-human-id <humanId>", "Preferred human assignee ID").option("--preferred-assistant-id <assistantId>", "Preferred AI assistant assignee ID").option("--preferred-teammate-id <teammateId>", "Preferred AI teammate assignee ID");
|
|
876
981
|
}
|
|
877
|
-
function
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
return text.endsWith("\n") ? text : `${text}
|
|
882
|
-
`;
|
|
883
|
-
}
|
|
884
|
-
async function prepareCaptureDirectory(outputDir) {
|
|
885
|
-
await mkdir(outputDir, { recursive: true });
|
|
886
|
-
await assertMoxtManagedOutputDir(outputDir);
|
|
887
|
-
await Promise.all([
|
|
888
|
-
rm(join(outputDir, RUNLOG_DIRECTORY_MARKER), { force: true }),
|
|
889
|
-
rm(join(outputDir, "run.json"), { force: true }),
|
|
890
|
-
rm(join(outputDir, "state.json"), { force: true }),
|
|
891
|
-
rm(join(outputDir, "upload.json"), { force: true }),
|
|
892
|
-
rm(join(outputDir, "events.jsonl"), { force: true }),
|
|
893
|
-
rm(join(outputDir, "transcript.jsonl"), { force: true }),
|
|
894
|
-
rm(join(outputDir, "events"), { recursive: true, force: true }),
|
|
895
|
-
rm(join(outputDir, "transcript"), { recursive: true, force: true })
|
|
896
|
-
]);
|
|
897
|
-
await writeRunlogDirectoryMarker(outputDir);
|
|
898
|
-
await mkdir(join(outputDir, "events"), { recursive: true });
|
|
899
|
-
await mkdir(join(outputDir, "transcript"), { recursive: true });
|
|
900
|
-
}
|
|
901
|
-
async function assertMoxtManagedOutputDir(outputDir) {
|
|
902
|
-
const entries = await readdir(outputDir);
|
|
903
|
-
const unmanaged = entries.filter((entry) => !RUNLOG_MANAGED_OUTPUT_NAMES.has(entry));
|
|
904
|
-
if (unmanaged.length > 0) {
|
|
905
|
-
throw new Error(`refusing to use run output directory with unmanaged files: ${unmanaged.slice(0, 5).join(", ")}`);
|
|
906
|
-
}
|
|
907
|
-
const meaningfulEntries = entries.filter((entry) => entry !== ".DS_Store");
|
|
908
|
-
if (meaningfulEntries.length === 0) {
|
|
909
|
-
return;
|
|
910
|
-
}
|
|
911
|
-
if (!entries.includes(RUNLOG_DIRECTORY_MARKER)) {
|
|
912
|
-
throw new Error("refusing to use run output directory without ownership marker");
|
|
982
|
+
function parsePositiveInteger(raw, label) {
|
|
983
|
+
const parsed = Number.parseInt(raw, 10);
|
|
984
|
+
if (!Number.isInteger(parsed) || parsed <= 0 || String(parsed) !== raw) {
|
|
985
|
+
throw new WorkflowOptionsError(`Error: ${label} must be a positive integer, got "${raw}".`);
|
|
913
986
|
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
987
|
+
return parsed;
|
|
988
|
+
}
|
|
989
|
+
function parseOptionalPositiveInteger(raw, label) {
|
|
990
|
+
if (raw == null || raw === "") return void 0;
|
|
991
|
+
return parsePositiveInteger(raw, label);
|
|
992
|
+
}
|
|
993
|
+
function parseStatusType(raw) {
|
|
994
|
+
if (raw == null || raw === "") return void 0;
|
|
995
|
+
const parsed = Number.parseInt(raw, 10);
|
|
996
|
+
if ((parsed === 1 || parsed === 2 || parsed === 3) && String(parsed) === raw) return parsed;
|
|
997
|
+
throw new WorkflowOptionsError("Error: --type must be one of 1 (todo), 2 (in-progress), or 3 (done).");
|
|
998
|
+
}
|
|
999
|
+
function parseRequiredStatusType(raw) {
|
|
1000
|
+
const value = parseStatusType(raw);
|
|
1001
|
+
if (value === void 0) {
|
|
1002
|
+
throw new WorkflowOptionsError("Error: --type is required.");
|
|
1003
|
+
}
|
|
1004
|
+
return value;
|
|
1005
|
+
}
|
|
1006
|
+
function parsePriority(raw) {
|
|
1007
|
+
if (raw == null || raw === "") return void 0;
|
|
1008
|
+
if (raw === "urgent") return 0;
|
|
1009
|
+
if (raw === "high") return 1;
|
|
1010
|
+
if (raw === "medium") return 2;
|
|
1011
|
+
if (raw === "low") return 3;
|
|
1012
|
+
throw new WorkflowOptionsError('Error: --priority must be "urgent", "high", "medium", or "low".');
|
|
1013
|
+
}
|
|
1014
|
+
function parseRequiredPriority(raw) {
|
|
1015
|
+
return parsePriority(raw ?? "medium") ?? 2;
|
|
1016
|
+
}
|
|
1017
|
+
function parseWorkflowScope(raw) {
|
|
1018
|
+
if (raw == null || raw === "") return "all";
|
|
1019
|
+
if (raw === "all" || raw === "owner_is_me") return raw;
|
|
1020
|
+
throw new WorkflowOptionsError('Error: --scope must be "all" or "owner_is_me".');
|
|
1021
|
+
}
|
|
1022
|
+
function parseTaskScope(raw) {
|
|
1023
|
+
if (raw == null || raw === "") return "all";
|
|
1024
|
+
if (raw === "all" || raw === "assigned_to_me" || raw === "subscribed_by_me" || raw === "owner_is_me") {
|
|
1025
|
+
return raw;
|
|
918
1026
|
}
|
|
919
|
-
|
|
920
|
-
|
|
1027
|
+
throw new WorkflowOptionsError(
|
|
1028
|
+
'Error: --scope must be "all", "assigned_to_me", "subscribed_by_me", or "owner_is_me".'
|
|
921
1029
|
);
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1030
|
+
}
|
|
1031
|
+
function parseFileIds(raw, label = "--file-ids") {
|
|
1032
|
+
if (raw == null || raw === "") return void 0;
|
|
1033
|
+
const ids = raw.split(",").map((item) => item.trim()).filter(Boolean);
|
|
1034
|
+
if (ids.length === 0) {
|
|
1035
|
+
throw new WorkflowOptionsError(`Error: ${label} must include at least one file ID.`);
|
|
925
1036
|
}
|
|
1037
|
+
return ids;
|
|
926
1038
|
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
} catch (error) {
|
|
932
|
-
throw new Error(`invalid ${fileName}: ${errorMessage(error)}`);
|
|
1039
|
+
function readDescFile(path2) {
|
|
1040
|
+
if (path2 == null || path2 === "") return void 0;
|
|
1041
|
+
if (!fs3.existsSync(path2)) {
|
|
1042
|
+
throw new WorkflowOptionsError(`Error: desc file not found: ${path2}`);
|
|
933
1043
|
}
|
|
934
|
-
const
|
|
935
|
-
if (
|
|
936
|
-
throw new
|
|
1044
|
+
const stat = fs3.statSync(path2);
|
|
1045
|
+
if (!stat.isFile()) {
|
|
1046
|
+
throw new WorkflowOptionsError(`Error: desc file is not a file: ${path2}`);
|
|
937
1047
|
}
|
|
938
|
-
|
|
939
|
-
|
|
1048
|
+
const buffer2 = fs3.readFileSync(path2);
|
|
1049
|
+
if (isBinaryContent(buffer2)) {
|
|
1050
|
+
throw new WorkflowOptionsError("Error: desc file must be UTF-8 text, not binary content.");
|
|
940
1051
|
}
|
|
941
|
-
|
|
942
|
-
|
|
1052
|
+
const content = buffer2.toString("utf8");
|
|
1053
|
+
if (content.length > MAX_DESC_LENGTH) {
|
|
1054
|
+
throw new WorkflowOptionsError(`Error: desc file must be at most ${MAX_DESC_LENGTH} characters.`);
|
|
943
1055
|
}
|
|
944
|
-
return
|
|
1056
|
+
return content;
|
|
945
1057
|
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
parsed = JSON.parse(await readFile(join(outputDir, RUNLOG_DIRECTORY_MARKER), "utf8"));
|
|
950
|
-
} catch (error) {
|
|
951
|
-
throw new Error(`invalid ${RUNLOG_DIRECTORY_MARKER}: ${errorMessage(error)}`);
|
|
1058
|
+
function readCommentContentFile(path2) {
|
|
1059
|
+
if (!fs3.existsSync(path2)) {
|
|
1060
|
+
throw new WorkflowOptionsError(`Error: comment content file not found: ${path2}`);
|
|
952
1061
|
}
|
|
953
|
-
const
|
|
954
|
-
if (
|
|
955
|
-
throw new
|
|
1062
|
+
const stat = fs3.statSync(path2);
|
|
1063
|
+
if (!stat.isFile()) {
|
|
1064
|
+
throw new WorkflowOptionsError(`Error: comment content path is not a file: ${path2}`);
|
|
956
1065
|
}
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
throw new
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
run_id: runId,
|
|
971
|
-
run: payload.run,
|
|
972
|
-
recording: payload.recording,
|
|
973
|
-
repo: payload.repo,
|
|
974
|
-
...payload.redaction_failed ? { redaction_failed: true } : {},
|
|
975
|
-
files: {
|
|
976
|
-
state: "state.json",
|
|
977
|
-
upload: "upload.json",
|
|
978
|
-
events: files.events,
|
|
979
|
-
transcript: files.transcript
|
|
980
|
-
}
|
|
981
|
-
};
|
|
982
|
-
await writeFile(join(outputDir, "run.json"), `${JSON.stringify(runFile, null, 2)}
|
|
983
|
-
`, "utf8");
|
|
984
|
-
}
|
|
985
|
-
async function writeRunlogDirectoryMarker(outputDir) {
|
|
986
|
-
await writeFile(
|
|
987
|
-
join(outputDir, RUNLOG_DIRECTORY_MARKER),
|
|
988
|
-
`${JSON.stringify({ schema_version: RUNLOG_DIRECTORY_MARKER_SCHEMA }, null, 2)}
|
|
989
|
-
`,
|
|
990
|
-
"utf8"
|
|
991
|
-
);
|
|
992
|
-
}
|
|
993
|
-
async function writeCaptureUploadFile(outputDir, runId) {
|
|
994
|
-
const path2 = join(outputDir, "upload.json");
|
|
1066
|
+
if (stat.size > MAX_COMMENT_CONTENT_BYTES) {
|
|
1067
|
+
throw new WorkflowOptionsError(
|
|
1068
|
+
`Error: comment content file must be at most ${MAX_COMMENT_CONTENT_LENGTH} characters.`
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
const buffer2 = fs3.readFileSync(path2);
|
|
1072
|
+
if (buffer2.includes(0)) {
|
|
1073
|
+
throw new WorkflowOptionsError("Error: comment content file must not contain NUL bytes.");
|
|
1074
|
+
}
|
|
1075
|
+
if (isBinaryContent(buffer2)) {
|
|
1076
|
+
throw new WorkflowOptionsError("Error: comment content file must be UTF-8 text, not binary content.");
|
|
1077
|
+
}
|
|
1078
|
+
let content;
|
|
995
1079
|
try {
|
|
996
|
-
|
|
997
|
-
return;
|
|
1080
|
+
content = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(buffer2);
|
|
998
1081
|
} catch {
|
|
1082
|
+
throw new WorkflowOptionsError("Error: comment content file must contain valid UTF-8 text.");
|
|
999
1083
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
async function writeCaptureStateFile(outputDir, runId, status, capturedAt) {
|
|
1010
|
-
const stateFile = {
|
|
1011
|
-
schema_version: "runlog.state.v1",
|
|
1012
|
-
run_id: runId,
|
|
1013
|
-
status,
|
|
1014
|
-
updated_at: capturedAt
|
|
1015
|
-
};
|
|
1016
|
-
await writeFile(join(outputDir, "state.json"), `${JSON.stringify(stateFile, null, 2)}
|
|
1017
|
-
`, "utf8");
|
|
1084
|
+
if (content.length === 0) {
|
|
1085
|
+
throw new WorkflowOptionsError("Error: comment content file must not be empty.");
|
|
1086
|
+
}
|
|
1087
|
+
if (content.length > MAX_COMMENT_CONTENT_LENGTH) {
|
|
1088
|
+
throw new WorkflowOptionsError(
|
|
1089
|
+
`Error: comment content file must be at most ${MAX_COMMENT_CONTENT_LENGTH} characters.`
|
|
1090
|
+
);
|
|
1091
|
+
}
|
|
1092
|
+
return content;
|
|
1018
1093
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1094
|
+
function parseAssigneeBody(options, allowClear) {
|
|
1095
|
+
const hasClear = options.clear === true;
|
|
1096
|
+
const humanId = parseOptionalPositiveInteger(options.humanId, "--human-id");
|
|
1097
|
+
const assistantId = parseOptionalPositiveInteger(options.assistantId, "--assistant-id");
|
|
1098
|
+
const teammateId = parseOptionalPositiveInteger(options.teammateId, "--teammate-id");
|
|
1099
|
+
const count = (humanId !== void 0 ? 1 : 0) + (assistantId !== void 0 ? 1 : 0) + (teammateId !== void 0 ? 1 : 0);
|
|
1100
|
+
if (hasClear && !allowClear) {
|
|
1101
|
+
throw new WorkflowOptionsError("Error: --clear is not supported here.");
|
|
1102
|
+
}
|
|
1103
|
+
if (hasClear && count > 0) {
|
|
1104
|
+
throw new WorkflowOptionsError("Error: --clear cannot be used with assignee ID options.");
|
|
1105
|
+
}
|
|
1106
|
+
if (count > 1) {
|
|
1107
|
+
throw new WorkflowOptionsError("Error: assignee ID options are mutually exclusive.");
|
|
1108
|
+
}
|
|
1109
|
+
if (hasClear) return {};
|
|
1110
|
+
if (humanId !== void 0) return { assigneeHumanId: humanId };
|
|
1111
|
+
if (assistantId !== void 0) return { assigneeAiAssistantId: assistantId };
|
|
1112
|
+
if (teammateId !== void 0) return { assigneeAiTeammateId: teammateId };
|
|
1113
|
+
return {};
|
|
1021
1114
|
}
|
|
1022
|
-
function
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
if (
|
|
1036
|
-
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
let content = "";
|
|
1040
|
-
let bytes = 0;
|
|
1041
|
-
let lines = 0;
|
|
1042
|
-
let oversizedLine = false;
|
|
1043
|
-
const flush2 = () => {
|
|
1044
|
-
if (content === "") {
|
|
1045
|
-
return;
|
|
1046
|
-
}
|
|
1047
|
-
const index = String(chunks.length + 1).padStart(6, "0");
|
|
1048
|
-
chunks.push({
|
|
1049
|
-
path: `${dir}/${prefix}-${index}.jsonl`,
|
|
1050
|
-
content,
|
|
1051
|
-
bytes,
|
|
1052
|
-
lines,
|
|
1053
|
-
oversized_line: oversizedLine
|
|
1054
|
-
});
|
|
1055
|
-
content = "";
|
|
1056
|
-
bytes = 0;
|
|
1057
|
-
lines = 0;
|
|
1058
|
-
oversizedLine = false;
|
|
1059
|
-
};
|
|
1060
|
-
for (const line of jsonlLines(text)) {
|
|
1061
|
-
const lineBytes = Buffer.byteLength(line);
|
|
1062
|
-
if (lineBytes > DATA_FILE_TARGET_BYTES) {
|
|
1063
|
-
flush2();
|
|
1064
|
-
content = line;
|
|
1065
|
-
bytes = lineBytes;
|
|
1066
|
-
lines = 1;
|
|
1067
|
-
oversizedLine = true;
|
|
1068
|
-
flush2();
|
|
1069
|
-
continue;
|
|
1070
|
-
}
|
|
1071
|
-
if (bytes > 0 && bytes + lineBytes > DATA_FILE_TARGET_BYTES) {
|
|
1072
|
-
flush2();
|
|
1073
|
-
}
|
|
1074
|
-
content += line;
|
|
1075
|
-
bytes += lineBytes;
|
|
1076
|
-
lines += 1;
|
|
1077
|
-
}
|
|
1078
|
-
flush2();
|
|
1079
|
-
return chunks;
|
|
1080
|
-
}
|
|
1081
|
-
function jsonlLines(text) {
|
|
1082
|
-
const normalized = normalizeJsonl(text);
|
|
1083
|
-
if (normalized === "") {
|
|
1084
|
-
return [];
|
|
1085
|
-
}
|
|
1086
|
-
return normalized.slice(0, -1).split("\n").map((line) => `${line}
|
|
1087
|
-
`);
|
|
1115
|
+
function parsePreferredAssigneeBody(options) {
|
|
1116
|
+
const hasClear = options.clearPreferredAssignee === true;
|
|
1117
|
+
const humanId = parseOptionalPositiveInteger(options.preferredHumanId, "--preferred-human-id");
|
|
1118
|
+
const assistantId = parseOptionalPositiveInteger(options.preferredAssistantId, "--preferred-assistant-id");
|
|
1119
|
+
const teammateId = parseOptionalPositiveInteger(options.preferredTeammateId, "--preferred-teammate-id");
|
|
1120
|
+
const count = (humanId !== void 0 ? 1 : 0) + (assistantId !== void 0 ? 1 : 0) + (teammateId !== void 0 ? 1 : 0);
|
|
1121
|
+
if (hasClear && count > 0) {
|
|
1122
|
+
throw new WorkflowOptionsError("Error: --clear-preferred-assignee cannot be used with preferred assignee ID options.");
|
|
1123
|
+
}
|
|
1124
|
+
if (count > 1) {
|
|
1125
|
+
throw new WorkflowOptionsError("Error: preferred assignee ID options are mutually exclusive.");
|
|
1126
|
+
}
|
|
1127
|
+
if (hasClear) return { preferredAssigneeHumanId: null };
|
|
1128
|
+
if (humanId !== void 0) return { preferredAssigneeHumanId: humanId };
|
|
1129
|
+
if (assistantId !== void 0) return { preferredAssigneeAiAssistantId: assistantId };
|
|
1130
|
+
if (teammateId !== void 0) return { preferredAssigneeAiTeammateId: teammateId };
|
|
1131
|
+
return {};
|
|
1088
1132
|
}
|
|
1089
|
-
function
|
|
1090
|
-
if (
|
|
1091
|
-
|
|
1092
|
-
}
|
|
1093
|
-
return `${events.map((event) => JSON.stringify(event)).join("\n")}
|
|
1094
|
-
`;
|
|
1095
|
-
}
|
|
1096
|
-
function normalizeEvents(transcript, startSeq, agent) {
|
|
1097
|
-
const events = [];
|
|
1098
|
-
for (const line of transcript.split("\n")) {
|
|
1099
|
-
if (line.trim() === "") {
|
|
1100
|
-
continue;
|
|
1101
|
-
}
|
|
1102
|
-
const raw = parseTranscriptLine(line);
|
|
1103
|
-
const event = agent === "codex" ? normalizeCodexEvent(raw, startSeq + events.length) : normalizeClaudeEvent(raw, startSeq + events.length);
|
|
1104
|
-
if (event !== null) {
|
|
1105
|
-
events.push(event);
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
return events;
|
|
1109
|
-
}
|
|
1110
|
-
function normalizeClaudeEvent(raw, seq) {
|
|
1111
|
-
if (raw === null) {
|
|
1112
|
-
return null;
|
|
1113
|
-
}
|
|
1114
|
-
const timestamp = stringValue(raw.timestamp);
|
|
1115
|
-
if (raw.type === "user") {
|
|
1116
|
-
const message = messageValue(raw.message);
|
|
1117
|
-
const text = textFromContent(message?.content);
|
|
1118
|
-
if (text === "") {
|
|
1119
|
-
return null;
|
|
1120
|
-
}
|
|
1121
|
-
return {
|
|
1122
|
-
schema_version: "runlog.events.v1",
|
|
1123
|
-
seq,
|
|
1124
|
-
type: "user_message",
|
|
1125
|
-
...timestamp === void 0 ? {} : { timestamp },
|
|
1126
|
-
text
|
|
1127
|
-
};
|
|
1128
|
-
}
|
|
1129
|
-
if (raw.type === "assistant") {
|
|
1130
|
-
const message = messageValue(raw.message);
|
|
1131
|
-
const text = textFromContent(message?.content);
|
|
1132
|
-
if (text === "") {
|
|
1133
|
-
return null;
|
|
1134
|
-
}
|
|
1135
|
-
const usage = usageValue(message?.usage);
|
|
1136
|
-
const model = stringValue(message?.model);
|
|
1137
|
-
const inputTokens = numberValue(usage?.input_tokens);
|
|
1138
|
-
const outputTokens = numberValue(usage?.output_tokens);
|
|
1139
|
-
return {
|
|
1140
|
-
schema_version: "runlog.events.v1",
|
|
1141
|
-
seq,
|
|
1142
|
-
type: "assistant_message",
|
|
1143
|
-
...timestamp === void 0 ? {} : { timestamp },
|
|
1144
|
-
...model === void 0 ? {} : { model },
|
|
1145
|
-
text,
|
|
1146
|
-
...inputTokens === void 0 ? {} : { input_tokens: inputTokens },
|
|
1147
|
-
...outputTokens === void 0 ? {} : { output_tokens: outputTokens }
|
|
1148
|
-
};
|
|
1149
|
-
}
|
|
1150
|
-
if (raw.type === "system" && raw.subtype === "turn_duration") {
|
|
1151
|
-
const durationMs = numberValue(raw.durationMs);
|
|
1152
|
-
const messageCount = numberValue(raw.messageCount);
|
|
1153
|
-
return {
|
|
1154
|
-
schema_version: "runlog.events.v1",
|
|
1155
|
-
seq,
|
|
1156
|
-
type: "turn_summary",
|
|
1157
|
-
...timestamp === void 0 ? {} : { timestamp },
|
|
1158
|
-
...durationMs === void 0 ? {} : { duration_ms: durationMs },
|
|
1159
|
-
...messageCount === void 0 ? {} : { message_count: messageCount }
|
|
1160
|
-
};
|
|
1161
|
-
}
|
|
1162
|
-
return null;
|
|
1163
|
-
}
|
|
1164
|
-
function normalizeCodexEvent(raw, seq) {
|
|
1165
|
-
if (raw === null || raw.type !== "event_msg") {
|
|
1166
|
-
return null;
|
|
1167
|
-
}
|
|
1168
|
-
const timestamp = stringValue(raw.timestamp);
|
|
1169
|
-
const payload = codexPayloadValue(raw.payload);
|
|
1170
|
-
const payloadType = stringValue(payload?.type);
|
|
1171
|
-
if (payloadType === "user_message") {
|
|
1172
|
-
const text = stringValue(payload?.message) ?? "";
|
|
1173
|
-
if (text === "") {
|
|
1174
|
-
return null;
|
|
1175
|
-
}
|
|
1176
|
-
return {
|
|
1177
|
-
schema_version: "runlog.events.v1",
|
|
1178
|
-
seq,
|
|
1179
|
-
type: "user_message",
|
|
1180
|
-
...timestamp === void 0 ? {} : { timestamp },
|
|
1181
|
-
text
|
|
1182
|
-
};
|
|
1183
|
-
}
|
|
1184
|
-
if (payloadType === "agent_message") {
|
|
1185
|
-
const text = stringValue(payload?.message) ?? "";
|
|
1186
|
-
if (text === "") {
|
|
1187
|
-
return null;
|
|
1188
|
-
}
|
|
1189
|
-
return {
|
|
1190
|
-
schema_version: "runlog.events.v1",
|
|
1191
|
-
seq,
|
|
1192
|
-
type: "assistant_message",
|
|
1193
|
-
...timestamp === void 0 ? {} : { timestamp },
|
|
1194
|
-
text
|
|
1195
|
-
};
|
|
1196
|
-
}
|
|
1197
|
-
if (payloadType === "task_complete") {
|
|
1198
|
-
const durationMs = numberValue(payload?.duration_ms);
|
|
1199
|
-
return {
|
|
1200
|
-
schema_version: "runlog.events.v1",
|
|
1201
|
-
seq,
|
|
1202
|
-
type: "turn_summary",
|
|
1203
|
-
...timestamp === void 0 ? {} : { timestamp },
|
|
1204
|
-
...durationMs === void 0 ? {} : { duration_ms: durationMs }
|
|
1205
|
-
};
|
|
1206
|
-
}
|
|
1207
|
-
return null;
|
|
1208
|
-
}
|
|
1209
|
-
function parseTranscriptLine(line) {
|
|
1210
|
-
try {
|
|
1211
|
-
const value = JSON.parse(line);
|
|
1212
|
-
return isObject(value) ? value : null;
|
|
1213
|
-
} catch {
|
|
1214
|
-
return null;
|
|
1133
|
+
function assertNonEmptyBody(body) {
|
|
1134
|
+
if (Object.keys(body).length === 0) {
|
|
1135
|
+
throw new WorkflowOptionsError("Error: at least one update option must be provided.");
|
|
1215
1136
|
}
|
|
1216
1137
|
}
|
|
1217
|
-
function
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
return "";
|
|
1223
|
-
}
|
|
1224
|
-
return content.map((part) => {
|
|
1225
|
-
const contentPart = contentPartValue(part);
|
|
1226
|
-
if (contentPart?.type !== "text") {
|
|
1227
|
-
return "";
|
|
1138
|
+
function buildQuery(params) {
|
|
1139
|
+
const query = new URLSearchParams();
|
|
1140
|
+
for (const [key, value] of Object.entries(params)) {
|
|
1141
|
+
if (value !== void 0) {
|
|
1142
|
+
query.set(key, String(value));
|
|
1228
1143
|
}
|
|
1229
|
-
return stringValue(contentPart.text) ?? "";
|
|
1230
|
-
}).filter((text) => text !== "").join("\n");
|
|
1231
|
-
}
|
|
1232
|
-
function messageValue(value) {
|
|
1233
|
-
return isObject(value) ? value : void 0;
|
|
1234
|
-
}
|
|
1235
|
-
function codexPayloadValue(value) {
|
|
1236
|
-
return isObject(value) ? value : void 0;
|
|
1237
|
-
}
|
|
1238
|
-
function usageValue(value) {
|
|
1239
|
-
return isObject(value) ? value : void 0;
|
|
1240
|
-
}
|
|
1241
|
-
function contentPartValue(value) {
|
|
1242
|
-
return isObject(value) ? value : void 0;
|
|
1243
|
-
}
|
|
1244
|
-
function stringValue(value) {
|
|
1245
|
-
return typeof value === "string" ? value : void 0;
|
|
1246
|
-
}
|
|
1247
|
-
function numberValue(value) {
|
|
1248
|
-
return typeof value === "number" ? value : void 0;
|
|
1249
|
-
}
|
|
1250
|
-
function isObject(value) {
|
|
1251
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1252
|
-
}
|
|
1253
|
-
function hasValue(value) {
|
|
1254
|
-
return value !== void 0 && value !== "";
|
|
1255
|
-
}
|
|
1256
|
-
function errorMessage(error) {
|
|
1257
|
-
if (error instanceof Error && error.message !== "") {
|
|
1258
|
-
return error.message;
|
|
1259
1144
|
}
|
|
1260
|
-
|
|
1261
|
-
}
|
|
1262
|
-
function objectValue(value) {
|
|
1263
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
1145
|
+
const text = query.toString();
|
|
1146
|
+
return text ? `?${text}` : "";
|
|
1264
1147
|
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
var SECRET_RULES = [
|
|
1269
|
-
{
|
|
1270
|
-
name: "private_key",
|
|
1271
|
-
pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
|
|
1272
|
-
replacement: (value, salt) => `<REDACTED:private_key:${tag(value, salt)}>`
|
|
1273
|
-
},
|
|
1274
|
-
{
|
|
1275
|
-
name: "bearer",
|
|
1276
|
-
pattern: /Bearer\s+[A-Za-z0-9._~+/=-]+/gi,
|
|
1277
|
-
replacement: (value, salt) => `<REDACTED:bearer:${tag(value, salt)}>`
|
|
1278
|
-
},
|
|
1279
|
-
{
|
|
1280
|
-
name: "basic",
|
|
1281
|
-
pattern: /Basic\s+[A-Za-z0-9+/=-]+/gi,
|
|
1282
|
-
replacement: (value, salt) => `<REDACTED:basic:${tag(value, salt)}>`
|
|
1283
|
-
},
|
|
1284
|
-
{
|
|
1285
|
-
name: "jwt",
|
|
1286
|
-
pattern: /eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g,
|
|
1287
|
-
replacement: (value, salt) => `<REDACTED:jwt:${tag(value, salt)}>`
|
|
1288
|
-
},
|
|
1289
|
-
{
|
|
1290
|
-
name: "github",
|
|
1291
|
-
pattern: /github_pat_[A-Za-z0-9_]{20,}/g,
|
|
1292
|
-
replacement: (value, salt) => `<REDACTED:github:${tag(value, salt)}>`
|
|
1293
|
-
},
|
|
1294
|
-
{
|
|
1295
|
-
name: "github",
|
|
1296
|
-
pattern: /github_pat_[A-Za-z0-9_]{4,}(?:\u2026|\.{3})/g,
|
|
1297
|
-
replacement: (value, salt) => `<REDACTED:github:${tag(value, salt)}>`
|
|
1298
|
-
},
|
|
1299
|
-
{
|
|
1300
|
-
name: "github",
|
|
1301
|
-
pattern: /gh[opsu]_[A-Za-z0-9_]{8,}/g,
|
|
1302
|
-
replacement: (value, salt) => `<REDACTED:github:${tag(value, salt)}>`
|
|
1303
|
-
},
|
|
1304
|
-
{
|
|
1305
|
-
name: "github",
|
|
1306
|
-
pattern: /gh[opsu]_[A-Za-z0-9_]{4,}(?:\u2026|\.{3})/g,
|
|
1307
|
-
replacement: (value, salt) => `<REDACTED:github:${tag(value, salt)}>`
|
|
1308
|
-
},
|
|
1309
|
-
{
|
|
1310
|
-
name: "openai",
|
|
1311
|
-
pattern: /sk-(?:proj-)?[A-Za-z0-9_-]{20,}/g,
|
|
1312
|
-
replacement: (value, salt) => `<REDACTED:openai:${tag(value, salt)}>`
|
|
1313
|
-
},
|
|
1314
|
-
{
|
|
1315
|
-
name: "openai",
|
|
1316
|
-
pattern: /sk-(?:proj-)?[A-Za-z0-9_-]{4,}(?:\u2026|\.{3})/g,
|
|
1317
|
-
replacement: (value, salt) => `<REDACTED:openai:${tag(value, salt)}>`
|
|
1318
|
-
},
|
|
1319
|
-
{
|
|
1320
|
-
name: "slack",
|
|
1321
|
-
pattern: /xox[baprs]-[A-Za-z0-9-]{10,}/g,
|
|
1322
|
-
replacement: (value, salt) => `<REDACTED:slack:${tag(value, salt)}>`
|
|
1323
|
-
},
|
|
1324
|
-
{
|
|
1325
|
-
name: "stripe",
|
|
1326
|
-
pattern: /(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{10,}/g,
|
|
1327
|
-
replacement: (value, salt) => `<REDACTED:stripe:${tag(value, salt)}>`
|
|
1328
|
-
},
|
|
1329
|
-
{
|
|
1330
|
-
name: "aws_key",
|
|
1331
|
-
pattern: /(?:AKIA|ASIA)[0-9A-Z]{16}/g,
|
|
1332
|
-
replacement: (value, salt) => `<REDACTED:aws_key:${tag(value, salt)}>`
|
|
1333
|
-
},
|
|
1334
|
-
{
|
|
1335
|
-
name: "aws_key",
|
|
1336
|
-
pattern: /(?:AKIA|ASIA)[0-9A-Z]{4,}(?:\u2026|\.{3})/g,
|
|
1337
|
-
replacement: (value, salt) => `<REDACTED:aws_key:${tag(value, salt)}>`
|
|
1338
|
-
}
|
|
1339
|
-
];
|
|
1340
|
-
var FIELD_QUOTED_PATTERN = /(["']?(?:password|passwd|pwd|secret|client_secret|token|access_token|refresh_token|authorization|cookie|set-cookie|api_key|apikey|private_key)["']?\s*[:=]\s*)(["'])(?:\\.|(?!\2)[\s\S])*?\2/gi;
|
|
1341
|
-
var FIELD_UNQUOTED_PATTERN = /(["']?(?:password|passwd|pwd|secret|client_secret|token|access_token|refresh_token|cookie|set-cookie|api_key|apikey|private_key)["']?\s*[:=]\s*)[^"',\n}\s]+/gi;
|
|
1342
|
-
function redactText(text, salt = "moxt-run-local-salt") {
|
|
1343
|
-
const hitCounts = /* @__PURE__ */ new Map();
|
|
1344
|
-
let redacted = text;
|
|
1345
|
-
for (const rule of SECRET_RULES) {
|
|
1346
|
-
redacted = redacted.replace(rule.pattern, (value) => {
|
|
1347
|
-
addHit(hitCounts, rule.name);
|
|
1348
|
-
return rule.replacement(value, salt);
|
|
1349
|
-
});
|
|
1350
|
-
}
|
|
1351
|
-
redacted = redacted.replace(FIELD_QUOTED_PATTERN, (_match, prefix, quote) => {
|
|
1352
|
-
addHit(hitCounts, "field");
|
|
1353
|
-
return `${prefix}${quote}<REDACTED:field>${quote}`;
|
|
1354
|
-
}).replace(FIELD_UNQUOTED_PATTERN, (_match, prefix) => {
|
|
1355
|
-
addHit(hitCounts, "field");
|
|
1356
|
-
return `${prefix}<REDACTED:field>`;
|
|
1357
|
-
});
|
|
1358
|
-
return {
|
|
1359
|
-
text: redacted,
|
|
1360
|
-
hits: [...hitCounts.entries()].map(([rule, count]) => ({ rule, count }))
|
|
1361
|
-
};
|
|
1362
|
-
}
|
|
1363
|
-
function tag(value, salt) {
|
|
1364
|
-
return createHmac("sha256", salt).update(value).digest("hex").slice(0, 8);
|
|
1148
|
+
function failResponse(response) {
|
|
1149
|
+
printError(`Error [${response.status}]: ${JSON.stringify(response.data)}`);
|
|
1150
|
+
process.exit(1);
|
|
1365
1151
|
}
|
|
1366
|
-
function
|
|
1367
|
-
|
|
1152
|
+
function priorityLabel(value) {
|
|
1153
|
+
switch (value) {
|
|
1154
|
+
case 0:
|
|
1155
|
+
return "urgent";
|
|
1156
|
+
case 1:
|
|
1157
|
+
return "high";
|
|
1158
|
+
case 2:
|
|
1159
|
+
return "medium";
|
|
1160
|
+
case 3:
|
|
1161
|
+
return "low";
|
|
1162
|
+
default: {
|
|
1163
|
+
const unsupportedValue = value;
|
|
1164
|
+
throw new Error(`Unsupported workflow task priority: ${String(unsupportedValue)}`);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
function statusTypeLabel(value) {
|
|
1169
|
+
switch (value) {
|
|
1170
|
+
case 1:
|
|
1171
|
+
return "todo";
|
|
1172
|
+
case 2:
|
|
1173
|
+
return "in-progress";
|
|
1174
|
+
case 3:
|
|
1175
|
+
return "done";
|
|
1176
|
+
default: {
|
|
1177
|
+
const unsupportedValue = value;
|
|
1178
|
+
throw new Error(`Unsupported workflow status type: ${String(unsupportedValue)}`);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
function formatAssignee(assignee) {
|
|
1183
|
+
if (!assignee) return "-";
|
|
1184
|
+
if (assignee.type === "human") return assignee.displayName ?? `human:${assignee.humanId}`;
|
|
1185
|
+
if (assignee.type === "assistant") return assignee.displayName ?? `assistant:${assignee.aiAssistantId}`;
|
|
1186
|
+
if (assignee.type === "teammate") return assignee.displayName ?? `teammate:${assignee.aiTeammateId}`;
|
|
1187
|
+
return "unknown";
|
|
1188
|
+
}
|
|
1189
|
+
function printWorkflow(item) {
|
|
1190
|
+
print(`${colors.bold}${item.fileId}${colors.reset} owner=${item.ownerHumanId ?? "-"} desc=${item.desc ?? "-"}`);
|
|
1191
|
+
}
|
|
1192
|
+
function indentation(size) {
|
|
1193
|
+
return " ".repeat(size);
|
|
1194
|
+
}
|
|
1195
|
+
function escapeTerminalControlCharacters(value) {
|
|
1196
|
+
return value.replace(/\r\n/g, "\n").replace(
|
|
1197
|
+
/[\u0000-\u0008\u000B-\u001F\u007F-\u009F]/g,
|
|
1198
|
+
(character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`
|
|
1199
|
+
);
|
|
1368
1200
|
}
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
return
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
const url = new URL(remote);
|
|
1377
|
-
url.username = "";
|
|
1378
|
-
url.password = "";
|
|
1379
|
-
return url.toString();
|
|
1380
|
-
} catch {
|
|
1381
|
-
return remote;
|
|
1201
|
+
function formatDetailScalar(value) {
|
|
1202
|
+
if (value === null) return "null";
|
|
1203
|
+
if (typeof value === "string") {
|
|
1204
|
+
const escaped = escapeTerminalControlCharacters(value);
|
|
1205
|
+
if (escaped === "") return '""';
|
|
1206
|
+
if (/\n/.test(escaped)) return JSON.stringify(escaped);
|
|
1207
|
+
return escaped;
|
|
1382
1208
|
}
|
|
1209
|
+
return String(value);
|
|
1383
1210
|
}
|
|
1384
|
-
function
|
|
1385
|
-
|
|
1386
|
-
return {
|
|
1387
|
-
...repo,
|
|
1388
|
-
remote: stripRemoteCredentials(repo.remote)
|
|
1389
|
-
};
|
|
1211
|
+
function printDetailField(name, value, indent = 0) {
|
|
1212
|
+
print(`${indentation(indent)}${name}: ${formatDetailScalar(value)}`);
|
|
1390
1213
|
}
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
var SESSION_START_TOLERANCE_MS = 1e3;
|
|
1396
|
-
function snapshotJsonl(root) {
|
|
1397
|
-
const snapshot = /* @__PURE__ */ new Map();
|
|
1398
|
-
if (!existsSync2(root)) {
|
|
1399
|
-
return snapshot;
|
|
1400
|
-
}
|
|
1401
|
-
collectJsonl(root, snapshot);
|
|
1402
|
-
return snapshot;
|
|
1403
|
-
}
|
|
1404
|
-
function matchSession(before, after, startedAtMs, options = {}) {
|
|
1405
|
-
const candidates = [];
|
|
1406
|
-
for (const [path2, current2] of after.entries()) {
|
|
1407
|
-
const previous = before.get(path2);
|
|
1408
|
-
if (previous === void 0) {
|
|
1409
|
-
if (wasChangedAfterStart(current2, startedAtMs)) {
|
|
1410
|
-
candidates.push({ path: path2, startOffset: 0 });
|
|
1411
|
-
}
|
|
1412
|
-
continue;
|
|
1413
|
-
}
|
|
1414
|
-
if (!options.newFilesOnly && current2.size > previous.size) {
|
|
1415
|
-
candidates.push({ path: path2, startOffset: previous.size });
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
if (candidates.length === 0) {
|
|
1419
|
-
return { match: "none", sessionFile: null, startOffset: 0 };
|
|
1420
|
-
}
|
|
1421
|
-
const trustedCandidates = options.isCandidate === void 0 ? candidates : candidates.filter((candidate) => options.isCandidate?.(candidate.path));
|
|
1422
|
-
if (trustedCandidates.length === 0) {
|
|
1423
|
-
return { match: "none", sessionFile: null, startOffset: 0 };
|
|
1424
|
-
}
|
|
1425
|
-
if (trustedCandidates.length === 1) {
|
|
1426
|
-
const [candidate] = trustedCandidates;
|
|
1427
|
-
if (candidate !== void 0) {
|
|
1428
|
-
return {
|
|
1429
|
-
match: "unique",
|
|
1430
|
-
sessionFile: candidate.path,
|
|
1431
|
-
startOffset: candidate.startOffset
|
|
1432
|
-
};
|
|
1433
|
-
}
|
|
1434
|
-
}
|
|
1435
|
-
return { match: "ambiguous", sessionFile: null, startOffset: 0 };
|
|
1436
|
-
}
|
|
1437
|
-
function matchKnownSessionFile(before, after, sessionFile) {
|
|
1438
|
-
const current2 = after.get(sessionFile);
|
|
1439
|
-
if (current2 === void 0) {
|
|
1440
|
-
return { match: "none", sessionFile: null, startOffset: 0 };
|
|
1441
|
-
}
|
|
1442
|
-
const previous = before.get(sessionFile);
|
|
1443
|
-
if (previous !== void 0 && current2.size <= previous.size) {
|
|
1444
|
-
return { match: "none", sessionFile: null, startOffset: 0 };
|
|
1214
|
+
function printDetailTextField(name, value, indent = 0) {
|
|
1215
|
+
if (value === null || value === "") {
|
|
1216
|
+
printDetailField(name, value, indent);
|
|
1217
|
+
return;
|
|
1445
1218
|
}
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
};
|
|
1451
|
-
}
|
|
1452
|
-
function recordingFromSessionMatch(match) {
|
|
1453
|
-
switch (match) {
|
|
1454
|
-
case "unique":
|
|
1455
|
-
return { found_chat: true, quality: "complete" };
|
|
1456
|
-
case "none":
|
|
1457
|
-
return { found_chat: false, quality: "not_found" };
|
|
1458
|
-
case "ambiguous":
|
|
1459
|
-
return { found_chat: false, quality: "uncertain" };
|
|
1460
|
-
case "prep_failed":
|
|
1461
|
-
return { found_chat: false, quality: "unavailable" };
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
function collectJsonl(dir, snapshot) {
|
|
1465
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
1466
|
-
const path2 = join2(dir, entry.name);
|
|
1467
|
-
if (entry.isDirectory()) {
|
|
1468
|
-
collectJsonl(path2, snapshot);
|
|
1469
|
-
continue;
|
|
1470
|
-
}
|
|
1471
|
-
if (!entry.isFile() || !entry.name.endsWith(".jsonl")) {
|
|
1472
|
-
continue;
|
|
1473
|
-
}
|
|
1474
|
-
const stat2 = statSync2(path2);
|
|
1475
|
-
snapshot.set(path2, {
|
|
1476
|
-
mtimeMs: stat2.mtimeMs,
|
|
1477
|
-
size: stat2.size
|
|
1478
|
-
});
|
|
1219
|
+
const escaped = escapeTerminalControlCharacters(value);
|
|
1220
|
+
print(`${indentation(indent)}${name}:`);
|
|
1221
|
+
for (const line of escaped.split("\n")) {
|
|
1222
|
+
print(`${indentation(indent + 2)}${line}`);
|
|
1479
1223
|
}
|
|
1480
1224
|
}
|
|
1481
|
-
function
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
// src/run/status.ts
|
|
1486
|
-
var SIGNAL_NUMBERS = /* @__PURE__ */ new Map([
|
|
1487
|
-
["SIGHUP", 1],
|
|
1488
|
-
["SIGINT", 2],
|
|
1489
|
-
["SIGQUIT", 3],
|
|
1490
|
-
["SIGKILL", 9],
|
|
1491
|
-
["SIGTERM", 15]
|
|
1492
|
-
]);
|
|
1493
|
-
function signalNumber(signal) {
|
|
1494
|
-
return SIGNAL_NUMBERS.get(signal) ?? 0;
|
|
1495
|
-
}
|
|
1496
|
-
function classifyExit(event, observedSignals) {
|
|
1497
|
-
if (event.errorCode !== void 0) {
|
|
1498
|
-
return {
|
|
1499
|
-
code: event.errorCode === "ENOENT" ? 127 : 126,
|
|
1500
|
-
status: "unknown"
|
|
1501
|
-
};
|
|
1502
|
-
}
|
|
1503
|
-
if (event.signal !== null) {
|
|
1504
|
-
return {
|
|
1505
|
-
code: 128 + signalNumber(event.signal),
|
|
1506
|
-
status: "cancelled"
|
|
1507
|
-
};
|
|
1508
|
-
}
|
|
1509
|
-
if (event.code !== null) {
|
|
1510
|
-
if (event.code === 0) {
|
|
1511
|
-
return { code: 0, status: "completed" };
|
|
1512
|
-
}
|
|
1513
|
-
const signal = event.code - 128;
|
|
1514
|
-
return {
|
|
1515
|
-
code: event.code,
|
|
1516
|
-
status: event.code > 128 && observedSignals.has(signal) ? "cancelled" : "failed"
|
|
1517
|
-
};
|
|
1518
|
-
}
|
|
1519
|
-
return { code: 1, status: "unknown" };
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
// src/run/live-capture.ts
|
|
1523
|
-
import { appendFile, open, writeFile as writeFile2 } from "fs/promises";
|
|
1524
|
-
import { join as join3 } from "path";
|
|
1525
|
-
var DATA_FILE_TARGET_BYTES2 = 1024 * 1024;
|
|
1526
|
-
var LIVE_CAPTURE_INTERVAL_MS = 250;
|
|
1527
|
-
function createLiveCapture(options) {
|
|
1528
|
-
return new LiveCaptureRecorder(options);
|
|
1529
|
-
}
|
|
1530
|
-
var LiveCaptureRecorder = class {
|
|
1531
|
-
enabled;
|
|
1532
|
-
outputs;
|
|
1533
|
-
env;
|
|
1534
|
-
runId;
|
|
1535
|
-
agent;
|
|
1536
|
-
startedAt;
|
|
1537
|
-
sessions;
|
|
1538
|
-
before;
|
|
1539
|
-
baselineOk;
|
|
1540
|
-
startedAtMs;
|
|
1541
|
-
knownSessionFile;
|
|
1542
|
-
isSessionCandidate;
|
|
1543
|
-
newSessionFilesOnly;
|
|
1544
|
-
openSessionFile;
|
|
1545
|
-
timer;
|
|
1546
|
-
pollInFlight = null;
|
|
1547
|
-
sessionFile = null;
|
|
1548
|
-
readOffset = 0;
|
|
1549
|
-
pendingText = "";
|
|
1550
|
-
nextSeq = 1;
|
|
1551
|
-
sessionMatch;
|
|
1552
|
-
captureFailed = false;
|
|
1553
|
-
constructor(options) {
|
|
1554
|
-
const outputDirs = captureOutputDirectories(options.env, options.runId);
|
|
1555
|
-
this.enabled = outputDirs.length > 0;
|
|
1556
|
-
this.outputs = outputDirs.map((outputDir) => new CaptureDirectoryOutput(outputDir));
|
|
1557
|
-
this.env = options.env;
|
|
1558
|
-
this.runId = options.runId;
|
|
1559
|
-
this.agent = options.agent;
|
|
1560
|
-
this.startedAt = options.startedAt;
|
|
1561
|
-
this.sessions = options.sessions;
|
|
1562
|
-
this.before = options.before;
|
|
1563
|
-
this.baselineOk = options.baselineOk;
|
|
1564
|
-
this.startedAtMs = options.startedAtMs;
|
|
1565
|
-
this.knownSessionFile = options.knownSessionFile;
|
|
1566
|
-
this.isSessionCandidate = options.isSessionCandidate;
|
|
1567
|
-
this.newSessionFilesOnly = options.newSessionFilesOnly ?? false;
|
|
1568
|
-
this.openSessionFile = options.openSessionFile;
|
|
1569
|
-
this.sessionMatch = options.baselineOk ? "none" : "prep_failed";
|
|
1570
|
-
}
|
|
1571
|
-
async start() {
|
|
1572
|
-
if (!this.enabled) {
|
|
1573
|
-
return;
|
|
1574
|
-
}
|
|
1575
|
-
await emitOpenCaptureDirectories(this.env, this.runId, this.startedAt);
|
|
1576
|
-
if (this.baselineOk) {
|
|
1577
|
-
this.timer = setInterval(() => {
|
|
1578
|
-
this.queuePoll();
|
|
1579
|
-
}, LIVE_CAPTURE_INTERVAL_MS);
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
async stop() {
|
|
1583
|
-
if (!this.enabled) {
|
|
1584
|
-
return;
|
|
1585
|
-
}
|
|
1586
|
-
if (this.timer !== void 0) {
|
|
1587
|
-
clearInterval(this.timer);
|
|
1588
|
-
this.timer = void 0;
|
|
1589
|
-
}
|
|
1590
|
-
if (this.pollInFlight !== null) {
|
|
1591
|
-
await this.pollInFlight;
|
|
1592
|
-
}
|
|
1593
|
-
await this.poll();
|
|
1594
|
-
await this.flushPendingText();
|
|
1595
|
-
}
|
|
1596
|
-
async finalize(payload, capturedAt) {
|
|
1597
|
-
if (!this.enabled) {
|
|
1598
|
-
return;
|
|
1599
|
-
}
|
|
1600
|
-
if (!isCompleteRecording(payload)) {
|
|
1601
|
-
await Promise.all(this.outputs.map((output) => output.invalidate()));
|
|
1602
|
-
}
|
|
1603
|
-
await finalizeCaptureDirectories(this.directoryPayload(payload), this.env, this.runId, capturedAt, this.files());
|
|
1604
|
-
}
|
|
1605
|
-
queuePoll() {
|
|
1606
|
-
if (this.pollInFlight !== null) {
|
|
1607
|
-
return;
|
|
1608
|
-
}
|
|
1609
|
-
this.pollInFlight = this.poll().finally(() => {
|
|
1610
|
-
this.pollInFlight = null;
|
|
1611
|
-
});
|
|
1612
|
-
}
|
|
1613
|
-
async poll() {
|
|
1614
|
-
if (!this.baselineOk || this.captureFailed) {
|
|
1615
|
-
return;
|
|
1616
|
-
}
|
|
1617
|
-
try {
|
|
1618
|
-
const snapshot = snapshotJsonl(this.sessions);
|
|
1619
|
-
if (this.sessionFile === null) {
|
|
1620
|
-
if (this.knownSessionFile !== void 0) {
|
|
1621
|
-
const matched2 = matchKnownSessionFile(this.before, snapshot, this.knownSessionFile);
|
|
1622
|
-
if (matched2.match === "unique" && matched2.sessionFile !== null) {
|
|
1623
|
-
this.sessionMatch = matched2.match;
|
|
1624
|
-
this.sessionFile = matched2.sessionFile;
|
|
1625
|
-
this.readOffset = matched2.startOffset;
|
|
1626
|
-
await this.captureNewText();
|
|
1627
|
-
return;
|
|
1628
|
-
}
|
|
1629
|
-
}
|
|
1630
|
-
const opened = this.openSessionFile?.() ?? { match: "none" };
|
|
1631
|
-
if (opened.match === "ambiguous") {
|
|
1632
|
-
this.sessionMatch = "ambiguous";
|
|
1633
|
-
return;
|
|
1634
|
-
}
|
|
1635
|
-
if (opened.match === "unique") {
|
|
1636
|
-
const matched2 = matchKnownSessionFile(this.before, snapshot, opened.sessionFile);
|
|
1637
|
-
this.sessionMatch = matched2.match;
|
|
1638
|
-
if (matched2.match === "unique" && matched2.sessionFile !== null) {
|
|
1639
|
-
this.sessionFile = matched2.sessionFile;
|
|
1640
|
-
this.readOffset = matched2.startOffset;
|
|
1641
|
-
await this.captureNewText();
|
|
1642
|
-
}
|
|
1643
|
-
return;
|
|
1644
|
-
}
|
|
1645
|
-
const matched = matchSession(this.before, snapshot, this.startedAtMs, this.matchOptions());
|
|
1646
|
-
this.sessionMatch = matched.match;
|
|
1647
|
-
if (matched.match !== "unique" || matched.sessionFile === null) {
|
|
1648
|
-
return;
|
|
1649
|
-
}
|
|
1650
|
-
this.sessionFile = matched.sessionFile;
|
|
1651
|
-
this.readOffset = matched.startOffset;
|
|
1652
|
-
}
|
|
1653
|
-
await this.captureNewText();
|
|
1654
|
-
} catch {
|
|
1655
|
-
this.captureFailed = true;
|
|
1656
|
-
}
|
|
1225
|
+
function printAssigneeDetail(name, assignee, indent = 0) {
|
|
1226
|
+
if (assignee === null) {
|
|
1227
|
+
printDetailField(name, null, indent);
|
|
1228
|
+
return;
|
|
1657
1229
|
}
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1230
|
+
print(`${indentation(indent)}${name}:`);
|
|
1231
|
+
printDetailField("type", assignee.type, indent + 2);
|
|
1232
|
+
switch (assignee.type) {
|
|
1233
|
+
case "human":
|
|
1234
|
+
printDetailField("humanId", assignee.humanId, indent + 2);
|
|
1235
|
+
printDetailField("displayName", assignee.displayName, indent + 2);
|
|
1236
|
+
printDetailField("avatarUrl", assignee.avatarUrl, indent + 2);
|
|
1664
1237
|
return;
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
async captureCompleteLines(text) {
|
|
1670
|
-
const combined = `${this.pendingText}${text}`;
|
|
1671
|
-
const lastNewline = combined.lastIndexOf("\n");
|
|
1672
|
-
if (lastNewline === -1) {
|
|
1673
|
-
this.pendingText = combined;
|
|
1238
|
+
case "assistant":
|
|
1239
|
+
printDetailField("aiAssistantId", assignee.aiAssistantId, indent + 2);
|
|
1240
|
+
printDetailField("displayName", assignee.displayName, indent + 2);
|
|
1241
|
+
printDetailField("avatarUrl", assignee.avatarUrl, indent + 2);
|
|
1674
1242
|
return;
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
}
|
|
1680
|
-
async flushPendingText() {
|
|
1681
|
-
if (this.pendingText === "") {
|
|
1243
|
+
case "teammate":
|
|
1244
|
+
printDetailField("aiTeammateId", assignee.aiTeammateId, indent + 2);
|
|
1245
|
+
printDetailField("displayName", assignee.displayName, indent + 2);
|
|
1246
|
+
printDetailField("avatarUrl", assignee.avatarUrl, indent + 2);
|
|
1682
1247
|
return;
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
}
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
this.events = new JsonlStreamWriter(outputDir, "events", "events");
|
|
1730
|
-
}
|
|
1731
|
-
async appendTranscript(text) {
|
|
1732
|
-
await this.transcript.appendText(text);
|
|
1733
|
-
}
|
|
1734
|
-
async appendEvents(text) {
|
|
1735
|
-
await this.events.appendText(text);
|
|
1736
|
-
}
|
|
1737
|
-
async invalidate() {
|
|
1738
|
-
await Promise.all([this.transcript.invalidate(), this.events.invalidate()]);
|
|
1739
|
-
}
|
|
1740
|
-
files() {
|
|
1741
|
-
return {
|
|
1742
|
-
events: this.events.files(),
|
|
1743
|
-
transcript: this.transcript.files()
|
|
1744
|
-
};
|
|
1745
|
-
}
|
|
1746
|
-
};
|
|
1747
|
-
var JsonlStreamWriter = class {
|
|
1748
|
-
outputDir;
|
|
1749
|
-
dir;
|
|
1750
|
-
prefix;
|
|
1751
|
-
writtenFiles = [];
|
|
1752
|
-
current = null;
|
|
1753
|
-
constructor(outputDir, dir, prefix) {
|
|
1754
|
-
this.outputDir = outputDir;
|
|
1755
|
-
this.dir = dir;
|
|
1756
|
-
this.prefix = prefix;
|
|
1757
|
-
}
|
|
1758
|
-
async appendText(text) {
|
|
1759
|
-
for (const line of jsonlLines2(text)) {
|
|
1760
|
-
await this.appendLine(line);
|
|
1761
|
-
}
|
|
1762
|
-
}
|
|
1763
|
-
async invalidate() {
|
|
1764
|
-
await Promise.all(
|
|
1765
|
-
this.writtenFiles.map(async (file) => {
|
|
1766
|
-
file.bytes = 0;
|
|
1767
|
-
file.lines = 0;
|
|
1768
|
-
file.oversized_line = false;
|
|
1769
|
-
await writeFile2(join3(this.outputDir, file.path), "", "utf8");
|
|
1770
|
-
})
|
|
1771
|
-
);
|
|
1772
|
-
this.current = null;
|
|
1773
|
-
}
|
|
1774
|
-
files() {
|
|
1775
|
-
return this.writtenFiles.map((file) => ({ ...file }));
|
|
1776
|
-
}
|
|
1777
|
-
async appendLine(line) {
|
|
1778
|
-
const lineBytes = Buffer.byteLength(line);
|
|
1779
|
-
if (lineBytes > DATA_FILE_TARGET_BYTES2) {
|
|
1780
|
-
this.current = null;
|
|
1781
|
-
const file2 = this.createFile();
|
|
1782
|
-
file2.bytes += lineBytes;
|
|
1783
|
-
file2.lines += 1;
|
|
1784
|
-
file2.oversized_line = true;
|
|
1785
|
-
await appendFile(join3(this.outputDir, file2.path), line, "utf8");
|
|
1786
|
-
this.current = null;
|
|
1787
|
-
return;
|
|
1788
|
-
}
|
|
1789
|
-
if (this.current !== null && this.current.bytes + lineBytes > DATA_FILE_TARGET_BYTES2) {
|
|
1790
|
-
this.current = null;
|
|
1791
|
-
}
|
|
1792
|
-
const file = this.current ?? this.createFile();
|
|
1793
|
-
file.bytes += lineBytes;
|
|
1794
|
-
file.lines += 1;
|
|
1795
|
-
await appendFile(join3(this.outputDir, file.path), line, "utf8");
|
|
1796
|
-
}
|
|
1797
|
-
createFile() {
|
|
1798
|
-
const index = String(this.writtenFiles.length + 1).padStart(6, "0");
|
|
1799
|
-
const file = {
|
|
1800
|
-
path: `${this.dir}/${this.prefix}-${index}.jsonl`,
|
|
1801
|
-
bytes: 0,
|
|
1802
|
-
lines: 0,
|
|
1803
|
-
oversized_line: false
|
|
1804
|
-
};
|
|
1805
|
-
this.writtenFiles.push(file);
|
|
1806
|
-
this.current = file;
|
|
1807
|
-
return file;
|
|
1808
|
-
}
|
|
1809
|
-
};
|
|
1810
|
-
function isCompleteRecording(payload) {
|
|
1811
|
-
return payload.recording.found_chat && payload.recording.quality === "complete";
|
|
1812
|
-
}
|
|
1813
|
-
function jsonlLines2(text) {
|
|
1814
|
-
if (text === "") {
|
|
1815
|
-
return [];
|
|
1816
|
-
}
|
|
1817
|
-
const normalized = text.endsWith("\n") ? text : `${text}
|
|
1818
|
-
`;
|
|
1819
|
-
return normalized.slice(0, -1).split("\n").map((line) => `${line}
|
|
1820
|
-
`);
|
|
1821
|
-
}
|
|
1822
|
-
async function readFileFromOffset(path2, offset) {
|
|
1823
|
-
const file = await open(path2, "r");
|
|
1824
|
-
try {
|
|
1825
|
-
const stat2 = await file.stat();
|
|
1826
|
-
const length = Math.max(0, stat2.size - offset);
|
|
1827
|
-
if (length === 0) {
|
|
1828
|
-
return { text: "", bytes: 0 };
|
|
1829
|
-
}
|
|
1830
|
-
const buffer2 = Buffer.alloc(length);
|
|
1831
|
-
const result = await file.read(buffer2, 0, length, offset);
|
|
1832
|
-
return { text: buffer2.subarray(0, result.bytesRead).toString("utf8"), bytes: result.bytesRead };
|
|
1833
|
-
} finally {
|
|
1834
|
-
await file.close();
|
|
1835
|
-
}
|
|
1836
|
-
}
|
|
1837
|
-
|
|
1838
|
-
// src/run/moxt-uploader.ts
|
|
1839
|
-
import { createHash } from "crypto";
|
|
1840
|
-
import { readdir as readdir2, readFile as readFile2, stat, writeFile as writeFile3 } from "fs/promises";
|
|
1841
|
-
import { arch as arch2, platform as platform2 } from "os";
|
|
1842
|
-
import { join as join4, relative } from "path";
|
|
1843
|
-
|
|
1844
|
-
// src/utils/cf-access.ts
|
|
1845
|
-
function cloudflareAccessHeaders(env = process.env) {
|
|
1846
|
-
const headers = {};
|
|
1847
|
-
const token = firstValue(env.MOXT_CF_ACCESS_TOKEN, env.CF_ACCESS_TOKEN);
|
|
1848
|
-
if (token !== void 0) {
|
|
1849
|
-
headers["CF-Access-Token"] = token;
|
|
1850
|
-
}
|
|
1851
|
-
const clientId = firstValue(env.MOXT_CF_ACCESS_CLIENT_ID, env.CF_ACCESS_CLIENT_ID);
|
|
1852
|
-
const clientSecret = firstValue(env.MOXT_CF_ACCESS_CLIENT_SECRET, env.CF_ACCESS_CLIENT_SECRET);
|
|
1853
|
-
if (clientId !== void 0 && clientSecret !== void 0) {
|
|
1854
|
-
headers["CF-Access-Client-Id"] = clientId;
|
|
1855
|
-
headers["CF-Access-Client-Secret"] = clientSecret;
|
|
1856
|
-
}
|
|
1857
|
-
return headers;
|
|
1858
|
-
}
|
|
1859
|
-
function firstValue(...values) {
|
|
1860
|
-
return values.find((value) => value !== void 0 && value !== "");
|
|
1861
|
-
}
|
|
1862
|
-
|
|
1863
|
-
// src/run/moxt-uploader.ts
|
|
1864
|
-
var DEFAULT_HOST2 = "api.moxt.ai";
|
|
1865
|
-
var DEFAULT_UPLOAD_TIMEOUT_MS = 3e4;
|
|
1866
|
-
async function uploadRunArtifacts(options) {
|
|
1867
|
-
const outputDirs = captureOutputDirectories(options.env, options.runId);
|
|
1868
|
-
const failures = [];
|
|
1869
|
-
for (const outputDir of outputDirs) {
|
|
1870
|
-
try {
|
|
1871
|
-
await uploadDirectory(outputDir, options.runId, options.upload, options.env);
|
|
1872
|
-
} catch (error) {
|
|
1873
|
-
failures.push(`${outputDir}: ${errorMessage2(error)}`);
|
|
1874
|
-
}
|
|
1875
|
-
}
|
|
1876
|
-
if (failures.length > 0) {
|
|
1877
|
-
throw new Error(failures.join("; "));
|
|
1878
|
-
}
|
|
1879
|
-
}
|
|
1880
|
-
async function uploadDirectory(outputDir, runId, config, env) {
|
|
1881
|
-
const files = await discoverUploadFiles(outputDir);
|
|
1882
|
-
const state = await readUploadState(outputDir, runId);
|
|
1883
|
-
for (const file of files) {
|
|
1884
|
-
const previous = state.files[file.artifactPath];
|
|
1885
|
-
if (previous?.status === "uploaded" && previous.sha256 === file.sha256 && previous.bytes === file.bytes) {
|
|
1886
|
-
continue;
|
|
1887
|
-
}
|
|
1888
|
-
const attempts = (previous?.attempts ?? 0) + 1;
|
|
1889
|
-
state.files[file.artifactPath] = {
|
|
1890
|
-
status: "uploading",
|
|
1891
|
-
bytes: file.bytes,
|
|
1892
|
-
sha256: file.sha256,
|
|
1893
|
-
attempts
|
|
1894
|
-
};
|
|
1895
|
-
state.status = "uploading";
|
|
1896
|
-
state.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1897
|
-
await writeUploadState(outputDir, state);
|
|
1898
|
-
try {
|
|
1899
|
-
const response = await uploadFile(runId, file, config, env);
|
|
1900
|
-
state.files[file.artifactPath] = {
|
|
1901
|
-
status: "uploaded",
|
|
1902
|
-
bytes: file.bytes,
|
|
1903
|
-
sha256: file.sha256,
|
|
1904
|
-
attempts,
|
|
1905
|
-
uploaded_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1906
|
-
...response.etag === void 0 ? {} : { etag: response.etag }
|
|
1907
|
-
};
|
|
1908
|
-
} catch (error) {
|
|
1909
|
-
state.files[file.artifactPath] = {
|
|
1910
|
-
status: "failed",
|
|
1911
|
-
bytes: file.bytes,
|
|
1912
|
-
sha256: file.sha256,
|
|
1913
|
-
attempts,
|
|
1914
|
-
failed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1915
|
-
error: errorMessage2(error)
|
|
1916
|
-
};
|
|
1917
|
-
}
|
|
1918
|
-
state.status = uploadStatus(
|
|
1919
|
-
state.files,
|
|
1920
|
-
files.map((candidate) => candidate.artifactPath)
|
|
1921
|
-
);
|
|
1922
|
-
state.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1923
|
-
await writeUploadState(outputDir, state);
|
|
1924
|
-
}
|
|
1925
|
-
state.status = uploadStatus(
|
|
1926
|
-
state.files,
|
|
1927
|
-
files.map((file) => file.artifactPath)
|
|
1248
|
+
case "unknown":
|
|
1249
|
+
printDetailField("humanId", assignee.humanId, indent + 2);
|
|
1250
|
+
printDetailField("aiAssistantId", assignee.aiAssistantId, indent + 2);
|
|
1251
|
+
printDetailField("aiTeammateId", assignee.aiTeammateId, indent + 2);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
function printStatusRemainingFields(item, indent) {
|
|
1255
|
+
printDetailField("name", item.name, indent);
|
|
1256
|
+
printDetailTextField("desc", item.desc, indent);
|
|
1257
|
+
printDetailField("type", `${item.type} (${statusTypeLabel(item.type)})`, indent);
|
|
1258
|
+
printAssigneeDetail("preferredAssignee", item.preferredAssignee, indent);
|
|
1259
|
+
printDetailField("order", item.order, indent);
|
|
1260
|
+
}
|
|
1261
|
+
function printStatusDetail(item) {
|
|
1262
|
+
print(`${colors.bold}Status${colors.reset}`);
|
|
1263
|
+
printDetailField("scopedId", item.scopedId);
|
|
1264
|
+
printStatusRemainingFields(item, 0);
|
|
1265
|
+
}
|
|
1266
|
+
function printWorkflowDetail(item) {
|
|
1267
|
+
print(`${colors.bold}Workflow${colors.reset}`);
|
|
1268
|
+
printDetailField("fileId", item.fileId);
|
|
1269
|
+
printDetailField("ownerHumanId", item.ownerHumanId);
|
|
1270
|
+
printDetailTextField("desc", item.desc);
|
|
1271
|
+
if (item.statusList.length === 0) {
|
|
1272
|
+
print("statusList: []");
|
|
1273
|
+
} else {
|
|
1274
|
+
print("statusList:");
|
|
1275
|
+
for (const status of item.statusList) {
|
|
1276
|
+
print(` - scopedId: ${status.scopedId}`);
|
|
1277
|
+
printStatusRemainingFields(status, 4);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
if (item.transitions.length === 0) {
|
|
1281
|
+
print("transitions: []");
|
|
1282
|
+
} else {
|
|
1283
|
+
print("transitions:");
|
|
1284
|
+
for (const transition of item.transitions) {
|
|
1285
|
+
print(` - statusFromScopedId: ${transition.statusFromScopedId}`);
|
|
1286
|
+
printDetailField("statusToScopedId", transition.statusToScopedId, 4);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
function printTaskSummary(item) {
|
|
1291
|
+
const runIds = item.agentRuns.map((run) => run.pipelineTriggerId).join(",") || "-";
|
|
1292
|
+
print(
|
|
1293
|
+
`${colors.bold}#${item.scopedId}${colors.reset} ${item.title} status=${item.statusScopedId} priority=${priorityLabel(item.priority)} assignee=${formatAssignee(item.assignee)} runs=${runIds}`
|
|
1928
1294
|
);
|
|
1929
|
-
state.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1930
|
-
await writeUploadState(outputDir, state);
|
|
1931
|
-
if (state.status === "failed") {
|
|
1932
|
-
throw new Error("one or more run files failed to upload");
|
|
1933
|
-
}
|
|
1934
|
-
}
|
|
1935
|
-
async function discoverUploadFiles(outputDir) {
|
|
1936
|
-
const paths = await uploadPaths(outputDir);
|
|
1937
|
-
const files = await Promise.all(
|
|
1938
|
-
paths.map(async (path2) => {
|
|
1939
|
-
const absolute = join4(outputDir, path2);
|
|
1940
|
-
const content = await readFile2(absolute);
|
|
1941
|
-
return {
|
|
1942
|
-
artifactPath: path2,
|
|
1943
|
-
bytes: content.byteLength,
|
|
1944
|
-
sha256: createHash("sha256").update(content).digest("hex"),
|
|
1945
|
-
contentText: content.toString("utf8")
|
|
1946
|
-
};
|
|
1947
|
-
})
|
|
1948
|
-
);
|
|
1949
|
-
return files.sort((a, b) => a.artifactPath.localeCompare(b.artifactPath));
|
|
1950
1295
|
}
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
}
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
controller.abort();
|
|
2000
|
-
}, timeoutMs);
|
|
2001
|
-
try {
|
|
2002
|
-
const response = await fetch(
|
|
2003
|
-
`${apiBaseUrl(env)}/workspaces/${encodeURIComponent(config.workspaceId)}/local-agent-runs/${encodeURIComponent(runId)}/files`,
|
|
2004
|
-
{
|
|
2005
|
-
method: "PUT",
|
|
2006
|
-
headers: {
|
|
2007
|
-
"Content-Type": "application/json",
|
|
2008
|
-
Authorization: `Bearer ${apiKey}`,
|
|
2009
|
-
"User-Agent": userAgent(),
|
|
2010
|
-
...cloudflareAccessHeaders(env)
|
|
2011
|
-
},
|
|
2012
|
-
signal: controller.signal,
|
|
2013
|
-
body: JSON.stringify({
|
|
2014
|
-
schema_version: "local_agent_run.upload_file.v1",
|
|
2015
|
-
artifact_path: file.artifactPath,
|
|
2016
|
-
bytes: file.bytes,
|
|
2017
|
-
sha256: file.sha256,
|
|
2018
|
-
encoding: "utf8",
|
|
2019
|
-
content_text: file.contentText
|
|
2020
|
-
})
|
|
2021
|
-
}
|
|
1296
|
+
function printTaskDetail(item) {
|
|
1297
|
+
print(`${colors.bold}Task${colors.reset}`);
|
|
1298
|
+
printDetailField("scopedId", item.scopedId);
|
|
1299
|
+
printDetailField("workflowFileId", item.workflowFileId);
|
|
1300
|
+
printDetailField("ownerHumanId", item.ownerHumanId);
|
|
1301
|
+
printDetailField("title", item.title);
|
|
1302
|
+
printDetailTextField("desc", item.desc);
|
|
1303
|
+
printDetailField("statusScopedId", item.statusScopedId);
|
|
1304
|
+
printDetailField("priority", `${item.priority} (${priorityLabel(item.priority)})`);
|
|
1305
|
+
printAssigneeDetail("assignee", item.assignee);
|
|
1306
|
+
if (item.linkedFileIds.length === 0) {
|
|
1307
|
+
print("linkedFileIds: []");
|
|
1308
|
+
} else {
|
|
1309
|
+
print("linkedFileIds:");
|
|
1310
|
+
for (const fileId of item.linkedFileIds) print(` - ${fileId}`);
|
|
1311
|
+
}
|
|
1312
|
+
printDetailField("subscribed", item.subscribed);
|
|
1313
|
+
if (item.agentRuns.length === 0) {
|
|
1314
|
+
print("agentRuns: []");
|
|
1315
|
+
} else {
|
|
1316
|
+
print("agentRuns:");
|
|
1317
|
+
for (const run of item.agentRuns) {
|
|
1318
|
+
print(` - pipelineTriggerId: ${run.pipelineTriggerId}`);
|
|
1319
|
+
printAssigneeDetail("executor", run.executor, 4);
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
printDetailField("createdAt", item.createdAt);
|
|
1323
|
+
printDetailField("latestActAt", item.latestActAt);
|
|
1324
|
+
}
|
|
1325
|
+
function printCommentDetail(item) {
|
|
1326
|
+
print(`${colors.bold}Comment${colors.reset}`);
|
|
1327
|
+
printDetailField("id", item.id);
|
|
1328
|
+
printDetailField("workflowFileId", item.workflowFileId);
|
|
1329
|
+
printDetailField("workflowTaskScopedId", item.workflowTaskScopedId);
|
|
1330
|
+
printDetailTextField("content", item.content);
|
|
1331
|
+
printAssigneeDetail("createdBy", item.createdBy);
|
|
1332
|
+
printDetailField("createdAt", item.createdAt);
|
|
1333
|
+
printDetailField("updatedAt", item.updatedAt);
|
|
1334
|
+
}
|
|
1335
|
+
function registerWorkflowCommand(program2) {
|
|
1336
|
+
const workflow = program2.command("workflow").description("Workflow operations");
|
|
1337
|
+
addWorkspaceOption(
|
|
1338
|
+
workflow.command("list").description("List accessible workflows").option("--scope <scope>", "Workflow scope: all or owner_is_me", "all")
|
|
1339
|
+
).action(async (options) => {
|
|
1340
|
+
const scope = runOrExit3(() => parseWorkflowScope(options.scope));
|
|
1341
|
+
startSpinner("Fetching workflows...");
|
|
1342
|
+
const response = await httpGet(
|
|
1343
|
+
`/workspaces/${options.workspace}/workflows${buildQuery({ scope })}`
|
|
2022
1344
|
);
|
|
2023
1345
|
if (!response.ok) {
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
const headerEtag = response.headers.get("etag") ?? void 0;
|
|
2027
|
-
const body = await responseJson(response);
|
|
2028
|
-
const { etag: bodyEtagValue } = isObject2(body) ? body : {};
|
|
2029
|
-
const bodyEtag = typeof bodyEtagValue === "string" ? bodyEtagValue : void 0;
|
|
2030
|
-
const etag = bodyEtag ?? headerEtag;
|
|
2031
|
-
return etag === void 0 ? {} : { etag };
|
|
2032
|
-
} catch (error) {
|
|
2033
|
-
if (controller.signal.aborted) {
|
|
2034
|
-
throw new Error(`upload timed out after ${timeoutMs}ms`);
|
|
2035
|
-
}
|
|
2036
|
-
throw error;
|
|
2037
|
-
} finally {
|
|
2038
|
-
clearTimeout(timeout);
|
|
2039
|
-
}
|
|
2040
|
-
}
|
|
2041
|
-
async function readUploadState(outputDir, runId) {
|
|
2042
|
-
try {
|
|
2043
|
-
const value = JSON.parse(await readFile2(join4(outputDir, "upload.json"), "utf8"));
|
|
2044
|
-
if (isUploadState(value, runId)) {
|
|
2045
|
-
return value;
|
|
2046
|
-
}
|
|
2047
|
-
} catch {
|
|
2048
|
-
}
|
|
2049
|
-
return {
|
|
2050
|
-
schema_version: "runlog.upload.v1",
|
|
2051
|
-
run_id: runId,
|
|
2052
|
-
status: "pending",
|
|
2053
|
-
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2054
|
-
files: {}
|
|
2055
|
-
};
|
|
2056
|
-
}
|
|
2057
|
-
async function writeUploadState(outputDir, state) {
|
|
2058
|
-
await writeFile3(join4(outputDir, "upload.json"), `${JSON.stringify(state, null, 2)}
|
|
2059
|
-
`, "utf8");
|
|
2060
|
-
}
|
|
2061
|
-
function uploadStatus(files, expectedPaths) {
|
|
2062
|
-
if (expectedPaths.length === 0) {
|
|
2063
|
-
return "pending";
|
|
2064
|
-
}
|
|
2065
|
-
const states = expectedPaths.map((path2) => files[path2]?.status ?? "pending");
|
|
2066
|
-
if (states.some((status) => status === "failed")) {
|
|
2067
|
-
return "failed";
|
|
2068
|
-
}
|
|
2069
|
-
if (states.every((status) => status === "uploaded")) {
|
|
2070
|
-
return "uploaded";
|
|
2071
|
-
}
|
|
2072
|
-
if (states.some((status) => status === "uploaded")) {
|
|
2073
|
-
return "partial";
|
|
2074
|
-
}
|
|
2075
|
-
return "uploading";
|
|
2076
|
-
}
|
|
2077
|
-
function isUploadState(value, runId) {
|
|
2078
|
-
if (!isObject2(value)) {
|
|
2079
|
-
return false;
|
|
2080
|
-
}
|
|
2081
|
-
const { schema_version: schemaVersion, run_id: valueRunId, files } = value;
|
|
2082
|
-
return schemaVersion === "runlog.upload.v1" && valueRunId === runId && isObject2(files);
|
|
2083
|
-
}
|
|
2084
|
-
async function responseJson(response) {
|
|
2085
|
-
try {
|
|
2086
|
-
return await response.json();
|
|
2087
|
-
} catch {
|
|
2088
|
-
return null;
|
|
2089
|
-
}
|
|
2090
|
-
}
|
|
2091
|
-
function apiBaseUrl(env) {
|
|
2092
|
-
return `https://${env.MOXT_HOST ?? DEFAULT_HOST2}/openapi/v1`;
|
|
2093
|
-
}
|
|
2094
|
-
function userAgent() {
|
|
2095
|
-
return `moxt-cli/${"0.3.3-moxt-run.1"} (${platform2()}/${arch2()}) node/${process.versions.node}`;
|
|
2096
|
-
}
|
|
2097
|
-
function errorMessage2(error) {
|
|
2098
|
-
if (error instanceof Error && error.message !== "") {
|
|
2099
|
-
return error.message;
|
|
2100
|
-
}
|
|
2101
|
-
return String(error);
|
|
2102
|
-
}
|
|
2103
|
-
function isObject2(value) {
|
|
2104
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2105
|
-
}
|
|
2106
|
-
|
|
2107
|
-
// src/run/process-session.ts
|
|
2108
|
-
import { execFileSync } from "child_process";
|
|
2109
|
-
import { isAbsolute, relative as relative2 } from "path";
|
|
2110
|
-
function findOpenSessionFile(options) {
|
|
2111
|
-
if ((options.platform ?? process.platform) !== "darwin") {
|
|
2112
|
-
return { match: "none" };
|
|
2113
|
-
}
|
|
2114
|
-
try {
|
|
2115
|
-
const psOutput = execFileSync("ps", ["-axo", "pid=,ppid="], {
|
|
2116
|
-
encoding: "utf8",
|
|
2117
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2118
|
-
});
|
|
2119
|
-
const pids = processTreePids(options.rootPid, psOutput);
|
|
2120
|
-
const lsofOutput = execFileSync("lsof", ["-F", "pn", "-p", pids.join(",")], {
|
|
2121
|
-
encoding: "utf8",
|
|
2122
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2123
|
-
});
|
|
2124
|
-
return openSessionFileFromLsof(lsofOutput, {
|
|
2125
|
-
sessionRoot: options.sessionRoot,
|
|
2126
|
-
...options.isCandidate === void 0 ? {} : { isCandidate: options.isCandidate }
|
|
2127
|
-
});
|
|
2128
|
-
} catch {
|
|
2129
|
-
return { match: "none" };
|
|
2130
|
-
}
|
|
2131
|
-
}
|
|
2132
|
-
function processTreePids(rootPid, psOutput) {
|
|
2133
|
-
const childrenByParent = /* @__PURE__ */ new Map();
|
|
2134
|
-
for (const line of psOutput.split("\n")) {
|
|
2135
|
-
const [pidText, ppidText] = line.trim().split(/\s+/, 2);
|
|
2136
|
-
const pid = Number(pidText);
|
|
2137
|
-
const ppid = Number(ppidText);
|
|
2138
|
-
if (!Number.isInteger(pid) || !Number.isInteger(ppid) || pid <= 0) {
|
|
2139
|
-
continue;
|
|
2140
|
-
}
|
|
2141
|
-
const children = childrenByParent.get(ppid) ?? [];
|
|
2142
|
-
children.push(pid);
|
|
2143
|
-
childrenByParent.set(ppid, children);
|
|
2144
|
-
}
|
|
2145
|
-
const pids = [];
|
|
2146
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2147
|
-
const queue = [rootPid];
|
|
2148
|
-
while (queue.length > 0) {
|
|
2149
|
-
const pid = queue.shift();
|
|
2150
|
-
if (seen.has(pid)) {
|
|
2151
|
-
continue;
|
|
2152
|
-
}
|
|
2153
|
-
seen.add(pid);
|
|
2154
|
-
pids.push(pid);
|
|
2155
|
-
queue.push(...childrenByParent.get(pid) ?? []);
|
|
2156
|
-
}
|
|
2157
|
-
return pids;
|
|
2158
|
-
}
|
|
2159
|
-
function openSessionFileFromLsof(lsofOutput, options) {
|
|
2160
|
-
const files = /* @__PURE__ */ new Set();
|
|
2161
|
-
for (const line of lsofOutput.split("\n")) {
|
|
2162
|
-
if (!line.startsWith("n")) {
|
|
2163
|
-
continue;
|
|
2164
|
-
}
|
|
2165
|
-
const path2 = line.slice(1);
|
|
2166
|
-
if (!path2.endsWith(".jsonl") || !isInside(options.sessionRoot, path2)) {
|
|
2167
|
-
continue;
|
|
2168
|
-
}
|
|
2169
|
-
if (options.isCandidate !== void 0 && !options.isCandidate(path2)) {
|
|
2170
|
-
continue;
|
|
2171
|
-
}
|
|
2172
|
-
files.add(alignPathToRoot(options.sessionRoot, path2));
|
|
2173
|
-
}
|
|
2174
|
-
if (files.size === 0) {
|
|
2175
|
-
return { match: "none" };
|
|
2176
|
-
}
|
|
2177
|
-
if (files.size === 1) {
|
|
2178
|
-
return { match: "unique", sessionFile: [...files].join("") };
|
|
2179
|
-
}
|
|
2180
|
-
return { match: "ambiguous" };
|
|
2181
|
-
}
|
|
2182
|
-
function isInside(root, path2) {
|
|
2183
|
-
if (isPathInside(root, path2)) {
|
|
2184
|
-
return true;
|
|
2185
|
-
}
|
|
2186
|
-
return isPathInside(normalizeDarwinVarPath(root), normalizeDarwinVarPath(path2));
|
|
2187
|
-
}
|
|
2188
|
-
function isPathInside(root, path2) {
|
|
2189
|
-
const fromRoot = relative2(root, path2);
|
|
2190
|
-
return fromRoot === "" || !fromRoot.startsWith("..") && !isAbsolute(fromRoot);
|
|
2191
|
-
}
|
|
2192
|
-
function normalizeDarwinVarPath(path2) {
|
|
2193
|
-
return path2.startsWith("/private/var/") ? path2.slice("/private".length) : path2;
|
|
2194
|
-
}
|
|
2195
|
-
function alignPathToRoot(root, path2) {
|
|
2196
|
-
if (root.startsWith("/var/") && path2.startsWith("/private/var/")) {
|
|
2197
|
-
return path2.slice("/private".length);
|
|
2198
|
-
}
|
|
2199
|
-
if (root.startsWith("/private/var/") && path2.startsWith("/var/")) {
|
|
2200
|
-
return `/private${path2}`;
|
|
2201
|
-
}
|
|
2202
|
-
return path2;
|
|
2203
|
-
}
|
|
2204
|
-
|
|
2205
|
-
// src/run/repo.ts
|
|
2206
|
-
import { execFileSync as execFileSync2 } from "child_process";
|
|
2207
|
-
function collectRepoFingerprint(cwd) {
|
|
2208
|
-
const root = git(["rev-parse", "--show-toplevel"], cwd);
|
|
2209
|
-
if (root === null) {
|
|
2210
|
-
return null;
|
|
2211
|
-
}
|
|
2212
|
-
return {
|
|
2213
|
-
root,
|
|
2214
|
-
remote: git(["config", "--get", "remote.origin.url"], cwd) ?? "",
|
|
2215
|
-
branch: git(["branch", "--show-current"], cwd) ?? "",
|
|
2216
|
-
head: git(["rev-parse", "HEAD"], cwd) ?? ""
|
|
2217
|
-
};
|
|
2218
|
-
}
|
|
2219
|
-
function git(args, cwd) {
|
|
2220
|
-
try {
|
|
2221
|
-
return execFileSync2("git", args, {
|
|
2222
|
-
cwd,
|
|
2223
|
-
encoding: "utf8",
|
|
2224
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2225
|
-
}).trim();
|
|
2226
|
-
} catch {
|
|
2227
|
-
return null;
|
|
2228
|
-
}
|
|
2229
|
-
}
|
|
2230
|
-
|
|
2231
|
-
// src/run/session-dir.ts
|
|
2232
|
-
import { closeSync, openSync, readSync, realpathSync } from "fs";
|
|
2233
|
-
import { join as join5, resolve } from "path";
|
|
2234
|
-
var CODEX_METADATA_READ_BYTES = 256 * 1024;
|
|
2235
|
-
function claudeProjectDir(cwd, home) {
|
|
2236
|
-
return join5(home, ".claude", "projects", encodeClaudeProjectPath(cwd));
|
|
2237
|
-
}
|
|
2238
|
-
function encodeClaudeProjectPath(cwd) {
|
|
2239
|
-
return cwd.replaceAll(/[^A-Za-z0-9]/g, "-");
|
|
2240
|
-
}
|
|
2241
|
-
function agentSessionDir(options) {
|
|
2242
|
-
switch (options.agent) {
|
|
2243
|
-
case "claude":
|
|
2244
|
-
return claudeProjectDir(options.cwd, options.home);
|
|
2245
|
-
case "codex":
|
|
2246
|
-
return codexSessionsDir(options.home, options.env);
|
|
2247
|
-
}
|
|
2248
|
-
}
|
|
2249
|
-
function agentSessionCandidateFilter(options) {
|
|
2250
|
-
switch (options.agent) {
|
|
2251
|
-
case "claude":
|
|
2252
|
-
return void 0;
|
|
2253
|
-
case "codex": {
|
|
2254
|
-
const expectedCwd = expectedCodexCwd(options.cwd, options.agentArgs);
|
|
2255
|
-
return (path2) => {
|
|
2256
|
-
try {
|
|
2257
|
-
const sessionCwd = codexSessionCwd(path2);
|
|
2258
|
-
return sessionCwd !== null && sameCodexCwd(sessionCwd, expectedCwd);
|
|
2259
|
-
} catch {
|
|
2260
|
-
return false;
|
|
2261
|
-
}
|
|
2262
|
-
};
|
|
2263
|
-
}
|
|
2264
|
-
}
|
|
2265
|
-
}
|
|
2266
|
-
function agentKnownSessionFile(options) {
|
|
2267
|
-
switch (options.agent) {
|
|
2268
|
-
case "claude": {
|
|
2269
|
-
const sessionId = claudeSessionIdArg(options.agentArgs) ?? (claudeForkSessionArg(options.agentArgs) ? void 0 : claudeResumeSessionIdArg(options.agentArgs));
|
|
2270
|
-
return sessionId === void 0 ? void 0 : join5(claudeProjectDir(options.cwd, options.home), `${sessionId}.jsonl`);
|
|
2271
|
-
}
|
|
2272
|
-
case "codex":
|
|
2273
|
-
return void 0;
|
|
2274
|
-
}
|
|
2275
|
-
}
|
|
2276
|
-
function codexSessionsDir(home, env = {}) {
|
|
2277
|
-
return join5(codexHome(home, env), "sessions");
|
|
2278
|
-
}
|
|
2279
|
-
function codexHome(home, env = {}) {
|
|
2280
|
-
const { CODEX_HOME: configured } = env;
|
|
2281
|
-
return hasValue2(configured) ? configured : join5(home, ".codex");
|
|
2282
|
-
}
|
|
2283
|
-
function expectedCodexCwd(cwd, agentArgs) {
|
|
2284
|
-
const cd = codexCdArg(agentArgs);
|
|
2285
|
-
return cd === void 0 ? resolve(cwd) : resolve(cwd, cd);
|
|
2286
|
-
}
|
|
2287
|
-
function claudeSessionIdArg(agentArgs) {
|
|
2288
|
-
const optionArgs = claudeOptionArgs(agentArgs);
|
|
2289
|
-
for (let index = 0; index < optionArgs.length; index += 1) {
|
|
2290
|
-
const arg = optionArgs[index];
|
|
2291
|
-
if (arg === "--session-id") {
|
|
2292
|
-
return optionArgs[index + 1];
|
|
2293
|
-
}
|
|
2294
|
-
if (arg.startsWith("--session-id=")) {
|
|
2295
|
-
return arg.slice("--session-id=".length);
|
|
2296
|
-
}
|
|
2297
|
-
}
|
|
2298
|
-
return void 0;
|
|
2299
|
-
}
|
|
2300
|
-
function claudeResumeSessionIdArg(agentArgs) {
|
|
2301
|
-
const optionArgs = claudeOptionArgs(agentArgs);
|
|
2302
|
-
for (let index = 0; index < optionArgs.length; index += 1) {
|
|
2303
|
-
const arg = optionArgs[index];
|
|
2304
|
-
if (arg === "--resume" || arg === "-r") {
|
|
2305
|
-
const value = optionArgs[index + 1];
|
|
2306
|
-
return isUuid(value) ? value : void 0;
|
|
2307
|
-
}
|
|
2308
|
-
if (arg.startsWith("--resume=")) {
|
|
2309
|
-
const value = arg.slice("--resume=".length);
|
|
2310
|
-
return isUuid(value) ? value : void 0;
|
|
2311
|
-
}
|
|
2312
|
-
}
|
|
2313
|
-
return void 0;
|
|
2314
|
-
}
|
|
2315
|
-
function codexSessionCwd(path2) {
|
|
2316
|
-
const text = readFilePrefix(path2, CODEX_METADATA_READ_BYTES);
|
|
2317
|
-
for (const line of text.split("\n")) {
|
|
2318
|
-
const cwd = codexLineCwd(line);
|
|
2319
|
-
if (cwd !== null) {
|
|
2320
|
-
return cwd;
|
|
2321
|
-
}
|
|
2322
|
-
}
|
|
2323
|
-
return null;
|
|
2324
|
-
}
|
|
2325
|
-
function codexCdArg(agentArgs) {
|
|
2326
|
-
const optionArgs = codexOptionArgs(agentArgs);
|
|
2327
|
-
for (let index = 0; index < optionArgs.length; index += 1) {
|
|
2328
|
-
const arg = optionArgs[index];
|
|
2329
|
-
if (arg === "--cd" || arg === "-C") {
|
|
2330
|
-
return optionArgs[index + 1];
|
|
2331
|
-
}
|
|
2332
|
-
if (arg.startsWith("--cd=")) {
|
|
2333
|
-
return arg.slice("--cd=".length);
|
|
2334
|
-
}
|
|
2335
|
-
}
|
|
2336
|
-
return void 0;
|
|
2337
|
-
}
|
|
2338
|
-
function codexOptionArgs(agentArgs) {
|
|
2339
|
-
const separatorIndex = agentArgs.indexOf("--");
|
|
2340
|
-
return separatorIndex === -1 ? agentArgs : agentArgs.slice(0, separatorIndex);
|
|
2341
|
-
}
|
|
2342
|
-
function sameCodexCwd(left, right) {
|
|
2343
|
-
return normalizeCodexCwd(left) === normalizeCodexCwd(right);
|
|
2344
|
-
}
|
|
2345
|
-
function normalizeCodexCwd(path2) {
|
|
2346
|
-
try {
|
|
2347
|
-
return realpathSync(path2);
|
|
2348
|
-
} catch {
|
|
2349
|
-
return resolve(path2);
|
|
2350
|
-
}
|
|
2351
|
-
}
|
|
2352
|
-
function claudeForkSessionArg(agentArgs) {
|
|
2353
|
-
return claudeOptionArgs(agentArgs).includes("--fork-session");
|
|
2354
|
-
}
|
|
2355
|
-
function claudeOptionArgs(agentArgs) {
|
|
2356
|
-
const separatorIndex = agentArgs.indexOf("--");
|
|
2357
|
-
return separatorIndex === -1 ? agentArgs : agentArgs.slice(0, separatorIndex);
|
|
2358
|
-
}
|
|
2359
|
-
function isUuid(value) {
|
|
2360
|
-
return value !== void 0 && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{12}$/i.test(value);
|
|
2361
|
-
}
|
|
2362
|
-
function codexLineCwd(line) {
|
|
2363
|
-
if (line.trim() === "") {
|
|
2364
|
-
return null;
|
|
2365
|
-
}
|
|
2366
|
-
let value;
|
|
2367
|
-
try {
|
|
2368
|
-
value = JSON.parse(line);
|
|
2369
|
-
} catch {
|
|
2370
|
-
return null;
|
|
2371
|
-
}
|
|
2372
|
-
const entry = rawCodexSessionLine(value);
|
|
2373
|
-
if (entry === null) {
|
|
2374
|
-
return null;
|
|
2375
|
-
}
|
|
2376
|
-
const type = entry.type;
|
|
2377
|
-
if (type !== "session_meta" && type !== "turn_context") {
|
|
2378
|
-
return null;
|
|
2379
|
-
}
|
|
2380
|
-
const payload = rawCodexSessionPayload(entry.payload);
|
|
2381
|
-
const cwd = payload?.cwd;
|
|
2382
|
-
return typeof cwd === "string" ? cwd : null;
|
|
2383
|
-
}
|
|
2384
|
-
function readFilePrefix(path2, bytes) {
|
|
2385
|
-
const fd = openSync(path2, "r");
|
|
2386
|
-
try {
|
|
2387
|
-
const buffer2 = Buffer.alloc(bytes);
|
|
2388
|
-
const read = readSync(fd, buffer2, 0, bytes, 0);
|
|
2389
|
-
return buffer2.subarray(0, read).toString("utf8");
|
|
2390
|
-
} finally {
|
|
2391
|
-
closeSync(fd);
|
|
2392
|
-
}
|
|
2393
|
-
}
|
|
2394
|
-
function objectValue2(value) {
|
|
2395
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
2396
|
-
}
|
|
2397
|
-
function rawCodexSessionLine(value) {
|
|
2398
|
-
return objectValue2(value);
|
|
2399
|
-
}
|
|
2400
|
-
function rawCodexSessionPayload(value) {
|
|
2401
|
-
return objectValue2(value);
|
|
2402
|
-
}
|
|
2403
|
-
function hasValue2(value) {
|
|
2404
|
-
return value !== void 0 && value !== "";
|
|
2405
|
-
}
|
|
2406
|
-
|
|
2407
|
-
// src/run/run-agent.ts
|
|
2408
|
-
var MAX_TRANSCRIPT_CAPTURE_BYTES = 1024 * 1024;
|
|
2409
|
-
var SESSION_SETTLE_TIMEOUT_MS = 1500;
|
|
2410
|
-
var SESSION_SETTLE_INTERVAL_MS = 100;
|
|
2411
|
-
var PROCESS_SESSION_POLL_INTERVAL_MS = 100;
|
|
2412
|
-
var CODEX_OPTIONS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
2413
|
-
"-a",
|
|
2414
|
-
"--add-dir",
|
|
2415
|
-
"--ask-for-approval",
|
|
2416
|
-
"-c",
|
|
2417
|
-
"-C",
|
|
2418
|
-
"--cd",
|
|
2419
|
-
"--color",
|
|
2420
|
-
"--config",
|
|
2421
|
-
"-i",
|
|
2422
|
-
"--image",
|
|
2423
|
-
"--local-provider",
|
|
2424
|
-
"-m",
|
|
2425
|
-
"--model",
|
|
2426
|
-
"-o",
|
|
2427
|
-
"--output-last-message",
|
|
2428
|
-
"--output-schema",
|
|
2429
|
-
"-p",
|
|
2430
|
-
"--profile",
|
|
2431
|
-
"--remote",
|
|
2432
|
-
"--remote-auth-token-env",
|
|
2433
|
-
"-s",
|
|
2434
|
-
"--sandbox"
|
|
2435
|
-
]);
|
|
2436
|
-
async function runLocalAgent(options) {
|
|
2437
|
-
const cwd = options.cwd ?? process.cwd();
|
|
2438
|
-
const env = options.env ?? process.env;
|
|
2439
|
-
const home = options.home ?? env.HOME ?? homedir2();
|
|
2440
|
-
const startedAtMs = Date.now();
|
|
2441
|
-
const startedAt = new Date(startedAtMs).toISOString();
|
|
2442
|
-
const runId = createCaptureRunId(startedAt, options.agent);
|
|
2443
|
-
const captureEnv = withDefaultCaptureOutput(env, home);
|
|
2444
|
-
try {
|
|
2445
|
-
await prepareCaptureOutputs(captureEnv, runId);
|
|
2446
|
-
} catch (error) {
|
|
2447
|
-
printPrepareError(options.agent, error);
|
|
2448
|
-
return 1;
|
|
2449
|
-
}
|
|
2450
|
-
const repo = collectRepoFingerprint(cwd);
|
|
2451
|
-
let baselineOk = false;
|
|
2452
|
-
let before = /* @__PURE__ */ new Map();
|
|
2453
|
-
let sessions = "";
|
|
2454
|
-
let isSessionCandidate;
|
|
2455
|
-
let knownSessionFile;
|
|
2456
|
-
let childRootPid;
|
|
2457
|
-
let openSessionFileMatch = { match: "none" };
|
|
2458
|
-
let processSessionTimer;
|
|
2459
|
-
const newSessionFilesOnly = shouldUseNewSessionFilesOnly(options.agent, options.agentArgs);
|
|
2460
|
-
try {
|
|
2461
|
-
const sessionOptions = {
|
|
2462
|
-
agent: options.agent,
|
|
2463
|
-
cwd,
|
|
2464
|
-
home,
|
|
2465
|
-
env: captureEnv,
|
|
2466
|
-
agentArgs: options.agentArgs
|
|
2467
|
-
};
|
|
2468
|
-
sessions = agentSessionDir(sessionOptions);
|
|
2469
|
-
isSessionCandidate = agentSessionCandidateFilter(sessionOptions);
|
|
2470
|
-
knownSessionFile = agentKnownSessionFile(sessionOptions);
|
|
2471
|
-
before = snapshotJsonl(sessions);
|
|
2472
|
-
baselineOk = true;
|
|
2473
|
-
} catch {
|
|
2474
|
-
baselineOk = false;
|
|
2475
|
-
}
|
|
2476
|
-
const rememberOpenSessionFile = () => {
|
|
2477
|
-
if (!baselineOk || sessions === "" || childRootPid === void 0 || openSessionFileMatch.match !== "none") {
|
|
2478
|
-
return openSessionFileMatch;
|
|
2479
|
-
}
|
|
2480
|
-
const matched = findOpenSessionFile({
|
|
2481
|
-
rootPid: childRootPid,
|
|
2482
|
-
sessionRoot: sessions,
|
|
2483
|
-
...isSessionCandidate === void 0 ? {} : { isCandidate: isSessionCandidate }
|
|
2484
|
-
});
|
|
2485
|
-
if (matched.match !== "none") {
|
|
2486
|
-
openSessionFileMatch = matched;
|
|
2487
|
-
}
|
|
2488
|
-
return openSessionFileMatch;
|
|
2489
|
-
};
|
|
2490
|
-
const stopProcessSessionWatcher = () => {
|
|
2491
|
-
if (processSessionTimer !== void 0) {
|
|
2492
|
-
clearInterval(processSessionTimer);
|
|
2493
|
-
processSessionTimer = void 0;
|
|
2494
|
-
}
|
|
2495
|
-
};
|
|
2496
|
-
const liveCapture = createLiveCapture({
|
|
2497
|
-
env: captureEnv,
|
|
2498
|
-
runId,
|
|
2499
|
-
agent: options.agent,
|
|
2500
|
-
startedAt,
|
|
2501
|
-
sessions,
|
|
2502
|
-
before,
|
|
2503
|
-
baselineOk,
|
|
2504
|
-
startedAtMs,
|
|
2505
|
-
...knownSessionFile === void 0 ? {} : { knownSessionFile },
|
|
2506
|
-
...isSessionCandidate === void 0 ? {} : { isSessionCandidate },
|
|
2507
|
-
...newSessionFilesOnly ? { newSessionFilesOnly: true } : {},
|
|
2508
|
-
openSessionFile: rememberOpenSessionFile
|
|
2509
|
-
});
|
|
2510
|
-
try {
|
|
2511
|
-
await liveCapture.start();
|
|
2512
|
-
} catch (error) {
|
|
2513
|
-
printError(
|
|
2514
|
-
[
|
|
2515
|
-
"moxt: cannot prepare run output",
|
|
2516
|
-
`reason: ${errorMessage3(error)}`,
|
|
2517
|
-
"",
|
|
2518
|
-
`${options.agent} was not started because this run cannot be recorded.`
|
|
2519
|
-
].join("\n")
|
|
2520
|
-
);
|
|
2521
|
-
return 1;
|
|
2522
|
-
}
|
|
2523
|
-
const observedSignals = /* @__PURE__ */ new Set();
|
|
2524
|
-
let child = null;
|
|
2525
|
-
let pendingForward = null;
|
|
2526
|
-
const observe = (signal) => {
|
|
2527
|
-
const number = signalNumber(signal);
|
|
2528
|
-
if (number !== 0) observedSignals.add(number);
|
|
2529
|
-
};
|
|
2530
|
-
const terminalSignalsReachChild = process.stdin.isTTY === true || process.stdout.isTTY === true || process.stderr.isTTY === true;
|
|
2531
|
-
const forwardInteractiveSignal = (signal) => {
|
|
2532
|
-
observe(signal);
|
|
2533
|
-
if (terminalSignalsReachChild) {
|
|
2534
|
-
return;
|
|
1346
|
+
stopSpinner(false, "Failed");
|
|
1347
|
+
failResponse(response);
|
|
2535
1348
|
}
|
|
2536
|
-
if (
|
|
2537
|
-
|
|
1349
|
+
if (response.data.workflows.length === 0) {
|
|
1350
|
+
stopSpinner(true, "No workflows found.");
|
|
2538
1351
|
return;
|
|
2539
1352
|
}
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
const onInt = () => forwardInteractiveSignal("SIGINT");
|
|
2543
|
-
const onQuit = () => forwardInteractiveSignal("SIGQUIT");
|
|
2544
|
-
const onTerm = () => {
|
|
2545
|
-
observe("SIGTERM");
|
|
2546
|
-
if (child === null) pendingForward = "SIGTERM";
|
|
2547
|
-
else safeKill(child, "SIGTERM");
|
|
2548
|
-
};
|
|
2549
|
-
const onHup = () => {
|
|
2550
|
-
observe("SIGHUP");
|
|
2551
|
-
if (child === null) pendingForward = "SIGHUP";
|
|
2552
|
-
else safeKill(child, "SIGHUP");
|
|
2553
|
-
};
|
|
2554
|
-
process.on("SIGINT", onInt);
|
|
2555
|
-
process.on("SIGQUIT", onQuit);
|
|
2556
|
-
process.on("SIGTERM", onTerm);
|
|
2557
|
-
process.on("SIGHUP", onHup);
|
|
2558
|
-
const spawned = spawn(options.agent, [...options.agentArgs], {
|
|
2559
|
-
cwd,
|
|
2560
|
-
env: captureEnv,
|
|
2561
|
-
stdio: "inherit"
|
|
2562
|
-
});
|
|
2563
|
-
child = spawned;
|
|
2564
|
-
childRootPid = spawned.pid;
|
|
2565
|
-
if (childRootPid !== void 0 && baselineOk) {
|
|
2566
|
-
rememberOpenSessionFile();
|
|
2567
|
-
processSessionTimer = setInterval(() => {
|
|
2568
|
-
rememberOpenSessionFile();
|
|
2569
|
-
}, PROCESS_SESSION_POLL_INTERVAL_MS);
|
|
2570
|
-
}
|
|
2571
|
-
if (pendingForward !== null) {
|
|
2572
|
-
safeKill(spawned, pendingForward);
|
|
2573
|
-
}
|
|
2574
|
-
const exit = await waitForExit(spawned, options.agent);
|
|
2575
|
-
stopProcessSessionWatcher();
|
|
2576
|
-
const onCaptureSignal = () => {
|
|
2577
|
-
};
|
|
2578
|
-
process.on("SIGINT", onCaptureSignal);
|
|
2579
|
-
process.on("SIGQUIT", onCaptureSignal);
|
|
2580
|
-
process.on("SIGTERM", onCaptureSignal);
|
|
2581
|
-
process.on("SIGHUP", onCaptureSignal);
|
|
2582
|
-
process.off("SIGINT", onInt);
|
|
2583
|
-
process.off("SIGQUIT", onQuit);
|
|
2584
|
-
process.off("SIGTERM", onTerm);
|
|
2585
|
-
process.off("SIGHUP", onHup);
|
|
2586
|
-
const classified = classifyExit(exit, observedSignals);
|
|
2587
|
-
const endedAtMs = exit.waitAt;
|
|
2588
|
-
const endedAt = new Date(endedAtMs).toISOString();
|
|
2589
|
-
const durationMs = endedAtMs - startedAtMs;
|
|
2590
|
-
let after;
|
|
2591
|
-
if (baselineOk) {
|
|
2592
|
-
try {
|
|
2593
|
-
after = await waitForSessionSettle(
|
|
2594
|
-
sessions,
|
|
2595
|
-
before,
|
|
2596
|
-
startedAtMs,
|
|
2597
|
-
isSessionCandidate,
|
|
2598
|
-
newSessionFilesOnly
|
|
2599
|
-
);
|
|
2600
|
-
} catch {
|
|
2601
|
-
}
|
|
2602
|
-
}
|
|
2603
|
-
try {
|
|
2604
|
-
await liveCapture.stop();
|
|
2605
|
-
} catch {
|
|
2606
|
-
}
|
|
2607
|
-
const payload = buildPayload({
|
|
2608
|
-
agent: options.agent,
|
|
2609
|
-
cwd,
|
|
2610
|
-
startedAt,
|
|
2611
|
-
endedAt,
|
|
2612
|
-
durationMs,
|
|
2613
|
-
exitCode: classified.code,
|
|
2614
|
-
status: classified.status,
|
|
2615
|
-
repo: sanitizeRepo(repo),
|
|
2616
|
-
baselineOk,
|
|
2617
|
-
sessions,
|
|
2618
|
-
before,
|
|
2619
|
-
after,
|
|
2620
|
-
startedAtMs,
|
|
2621
|
-
knownSessionFile,
|
|
2622
|
-
isSessionCandidate,
|
|
2623
|
-
newSessionFilesOnly,
|
|
2624
|
-
openSessionFileMatch
|
|
2625
|
-
});
|
|
2626
|
-
let finalized = false;
|
|
2627
|
-
try {
|
|
2628
|
-
if (liveCapture.enabled) {
|
|
2629
|
-
await liveCapture.finalize(payload, (/* @__PURE__ */ new Date()).toISOString());
|
|
2630
|
-
} else {
|
|
2631
|
-
await writeCaptureDirectoryPayload(payload, captureEnv, runId, (/* @__PURE__ */ new Date()).toISOString());
|
|
2632
|
-
}
|
|
2633
|
-
finalized = true;
|
|
2634
|
-
} catch (error) {
|
|
2635
|
-
printError(`moxt: cannot finalize run artifact: ${errorMessage3(error)}`);
|
|
2636
|
-
} finally {
|
|
2637
|
-
process.off("SIGINT", onCaptureSignal);
|
|
2638
|
-
process.off("SIGQUIT", onCaptureSignal);
|
|
2639
|
-
process.off("SIGTERM", onCaptureSignal);
|
|
2640
|
-
process.off("SIGHUP", onCaptureSignal);
|
|
2641
|
-
}
|
|
2642
|
-
if (finalized && options.upload !== void 0) {
|
|
2643
|
-
try {
|
|
2644
|
-
await uploadRunArtifacts({
|
|
2645
|
-
env: captureEnv,
|
|
2646
|
-
runId,
|
|
2647
|
-
upload: options.upload
|
|
2648
|
-
});
|
|
2649
|
-
} catch (error) {
|
|
2650
|
-
printError(`moxt: cannot upload run artifact: ${errorMessage3(error)}`);
|
|
2651
|
-
}
|
|
2652
|
-
}
|
|
2653
|
-
return classified.code;
|
|
2654
|
-
}
|
|
2655
|
-
async function waitForSessionSettle(sessions, before, startedAtMs, isSessionCandidate, newSessionFilesOnly) {
|
|
2656
|
-
const deadline = Date.now() + SESSION_SETTLE_TIMEOUT_MS;
|
|
2657
|
-
let after = snapshotJsonl(sessions);
|
|
2658
|
-
while (!hasSessionChange(before, after, startedAtMs, isSessionCandidate, newSessionFilesOnly) && Date.now() < deadline) {
|
|
2659
|
-
await sleep(SESSION_SETTLE_INTERVAL_MS);
|
|
2660
|
-
after = snapshotJsonl(sessions);
|
|
2661
|
-
}
|
|
2662
|
-
return after;
|
|
2663
|
-
}
|
|
2664
|
-
function hasSessionChange(before, after, startedAtMs, isSessionCandidate, newSessionFilesOnly) {
|
|
2665
|
-
const matched = matchSession(before, after, startedAtMs, matchOptions(isSessionCandidate, newSessionFilesOnly));
|
|
2666
|
-
if (matched.match !== "unique") {
|
|
2667
|
-
return matched.match !== "none";
|
|
2668
|
-
}
|
|
2669
|
-
if (matched.sessionFile === null) {
|
|
2670
|
-
return false;
|
|
2671
|
-
}
|
|
2672
|
-
const snapshot = after.get(matched.sessionFile);
|
|
2673
|
-
return snapshot !== void 0 && snapshot.size > matched.startOffset;
|
|
2674
|
-
}
|
|
2675
|
-
function sleep(ms) {
|
|
2676
|
-
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
2677
|
-
}
|
|
2678
|
-
function safeKill(child, signal) {
|
|
2679
|
-
try {
|
|
2680
|
-
child.kill(signal);
|
|
2681
|
-
} catch {
|
|
2682
|
-
}
|
|
2683
|
-
}
|
|
2684
|
-
function waitForExit(child, agent) {
|
|
2685
|
-
return new Promise((resolve2) => {
|
|
2686
|
-
child.once("error", (error) => {
|
|
2687
|
-
const errorCode = error.code ?? "UNKNOWN";
|
|
2688
|
-
const reason = errorCode === "ENOENT" ? "command not found" : error.message;
|
|
2689
|
-
printError(`moxt: cannot start ${agent}: ${reason}`);
|
|
2690
|
-
resolve2({
|
|
2691
|
-
code: null,
|
|
2692
|
-
signal: null,
|
|
2693
|
-
waitAt: Date.now(),
|
|
2694
|
-
errorCode
|
|
2695
|
-
});
|
|
2696
|
-
});
|
|
2697
|
-
child.once("exit", (code, signal) => {
|
|
2698
|
-
resolve2({ code, signal, waitAt: Date.now() });
|
|
2699
|
-
});
|
|
2700
|
-
});
|
|
2701
|
-
}
|
|
2702
|
-
function buildPayload(input) {
|
|
2703
|
-
let sessionMatch = input.baselineOk ? "none" : "prep_failed";
|
|
2704
|
-
let transcript;
|
|
2705
|
-
let redactionFailed = false;
|
|
2706
|
-
if (input.baselineOk) {
|
|
2707
|
-
try {
|
|
2708
|
-
const after = input.after ?? snapshotJsonl(input.sessions);
|
|
2709
|
-
const knownMatched = input.knownSessionFile === void 0 ? null : matchKnownSessionFile(input.before, after, input.knownSessionFile);
|
|
2710
|
-
const matched = knownMatched?.match === "unique" ? knownMatched : input.openSessionFileMatch.match === "unique" ? matchKnownSessionFile(input.before, after, input.openSessionFileMatch.sessionFile) : input.openSessionFileMatch.match === "ambiguous" ? {
|
|
2711
|
-
match: "ambiguous",
|
|
2712
|
-
sessionFile: null,
|
|
2713
|
-
startOffset: 0
|
|
2714
|
-
} : matchSession(
|
|
2715
|
-
input.before,
|
|
2716
|
-
after,
|
|
2717
|
-
input.startedAtMs,
|
|
2718
|
-
matchOptions(input.isSessionCandidate, input.newSessionFilesOnly)
|
|
2719
|
-
);
|
|
2720
|
-
sessionMatch = matched.match;
|
|
2721
|
-
if (matched.match === "unique" && matched.sessionFile !== null) {
|
|
2722
|
-
try {
|
|
2723
|
-
transcript = redactText(readFileFromOffset2(matched.sessionFile, matched.startOffset)).text;
|
|
2724
|
-
} catch {
|
|
2725
|
-
redactionFailed = true;
|
|
2726
|
-
}
|
|
2727
|
-
}
|
|
2728
|
-
} catch {
|
|
2729
|
-
sessionMatch = "prep_failed";
|
|
2730
|
-
}
|
|
2731
|
-
}
|
|
2732
|
-
return {
|
|
2733
|
-
...transcript === void 0 ? {} : { transcript_redacted: transcript },
|
|
2734
|
-
...redactionFailed ? { redaction_failed: true } : {},
|
|
2735
|
-
run: {
|
|
2736
|
-
agent: input.agent,
|
|
2737
|
-
cwd: input.cwd,
|
|
2738
|
-
git_root: input.repo?.root ?? null,
|
|
2739
|
-
started_at: input.startedAt,
|
|
2740
|
-
ended_at: input.endedAt,
|
|
2741
|
-
duration_ms: input.durationMs,
|
|
2742
|
-
status: input.status,
|
|
2743
|
-
exit_code: input.exitCode
|
|
2744
|
-
},
|
|
2745
|
-
recording: recordingFromSessionMatch(sessionMatch),
|
|
2746
|
-
repo: input.repo
|
|
2747
|
-
};
|
|
2748
|
-
}
|
|
2749
|
-
function matchOptions(isSessionCandidate, newSessionFilesOnly) {
|
|
2750
|
-
return {
|
|
2751
|
-
...isSessionCandidate === void 0 ? {} : { isCandidate: isSessionCandidate },
|
|
2752
|
-
...newSessionFilesOnly ? { newFilesOnly: true } : {}
|
|
2753
|
-
};
|
|
2754
|
-
}
|
|
2755
|
-
function shouldUseNewSessionFilesOnly(agent, agentArgs) {
|
|
2756
|
-
return agent === "codex" && !isCodexResumeInvocation(agentArgs);
|
|
2757
|
-
}
|
|
2758
|
-
function isCodexResumeInvocation(agentArgs) {
|
|
2759
|
-
const command = nextCodexCommandArg(agentArgs, 0);
|
|
2760
|
-
if (command === null) {
|
|
2761
|
-
return false;
|
|
2762
|
-
}
|
|
2763
|
-
if (command.value === "resume") {
|
|
2764
|
-
return true;
|
|
2765
|
-
}
|
|
2766
|
-
if (command.value !== "exec" && command.value !== "e") {
|
|
2767
|
-
return false;
|
|
2768
|
-
}
|
|
2769
|
-
return nextCodexCommandArg(agentArgs, command.index + 1)?.value === "resume";
|
|
2770
|
-
}
|
|
2771
|
-
function nextCodexCommandArg(agentArgs, startIndex) {
|
|
2772
|
-
for (let index = startIndex; index < agentArgs.length; index += 1) {
|
|
2773
|
-
const arg = agentArgs[index];
|
|
2774
|
-
if (arg === void 0) {
|
|
2775
|
-
continue;
|
|
2776
|
-
}
|
|
2777
|
-
if (arg === "--") {
|
|
2778
|
-
return null;
|
|
2779
|
-
}
|
|
2780
|
-
if (arg.startsWith("-")) {
|
|
2781
|
-
if (!arg.includes("=") && CODEX_OPTIONS_WITH_VALUE.has(arg)) {
|
|
2782
|
-
index += 1;
|
|
2783
|
-
}
|
|
2784
|
-
continue;
|
|
2785
|
-
}
|
|
2786
|
-
return { value: arg, index };
|
|
2787
|
-
}
|
|
2788
|
-
return null;
|
|
2789
|
-
}
|
|
2790
|
-
function readFileFromOffset2(path2, offset) {
|
|
2791
|
-
const fd = openSync2(path2, "r");
|
|
2792
|
-
try {
|
|
2793
|
-
const stat2 = fstatSync(fd);
|
|
2794
|
-
const length = Math.max(0, stat2.size - offset);
|
|
2795
|
-
if (length > MAX_TRANSCRIPT_CAPTURE_BYTES) {
|
|
2796
|
-
throw new Error("transcript capture exceeds maximum size");
|
|
2797
|
-
}
|
|
2798
|
-
const buffer2 = Buffer.alloc(length);
|
|
2799
|
-
if (length === 0) {
|
|
2800
|
-
return "";
|
|
2801
|
-
}
|
|
2802
|
-
readSync2(fd, buffer2, 0, length, offset);
|
|
2803
|
-
return buffer2.toString("utf8");
|
|
2804
|
-
} finally {
|
|
2805
|
-
closeSync2(fd);
|
|
2806
|
-
}
|
|
2807
|
-
}
|
|
2808
|
-
function printPrepareError(agent, error) {
|
|
2809
|
-
if (error instanceof CaptureOutputPrepareError) {
|
|
2810
|
-
printError(
|
|
2811
|
-
[
|
|
2812
|
-
"moxt: cannot create run output directory",
|
|
2813
|
-
`path: ${error.path}`,
|
|
2814
|
-
`reason: ${error.message}`,
|
|
2815
|
-
"",
|
|
2816
|
-
`${agent} was not started because this run cannot be recorded.`
|
|
2817
|
-
].join("\n")
|
|
2818
|
-
);
|
|
2819
|
-
return;
|
|
2820
|
-
}
|
|
2821
|
-
printError(
|
|
2822
|
-
[
|
|
2823
|
-
"moxt: cannot prepare run output",
|
|
2824
|
-
`reason: ${errorMessage3(error)}`,
|
|
2825
|
-
"",
|
|
2826
|
-
`${agent} was not started because this run cannot be recorded.`
|
|
2827
|
-
].join("\n")
|
|
2828
|
-
);
|
|
2829
|
-
}
|
|
2830
|
-
function errorMessage3(error) {
|
|
2831
|
-
if (error instanceof Error && error.message !== "") {
|
|
2832
|
-
return error.message;
|
|
2833
|
-
}
|
|
2834
|
-
return String(error);
|
|
2835
|
-
}
|
|
2836
|
-
|
|
2837
|
-
// src/commands/run.ts
|
|
2838
|
-
function registerRunCommand(program2) {
|
|
2839
|
-
program2.command("run").description("Run a local agent and capture its work as Moxt context").option("-w, --workspace <workspaceId>", "Workspace ID for upload mode").argument("<agent>", "Local agent to run: claude or codex").argument("[agentArgs...]", "Arguments passed to the local agent after --").allowUnknownOption(true).allowExcessArguments(true).action(async (agentArg, agentArgs, options) => {
|
|
2840
|
-
const agent = parseAgent(agentArg);
|
|
2841
|
-
const upload = resolveUploadOptions(options);
|
|
2842
|
-
const exitCode = await runLocalAgentOrExit(agent, agentArgs ?? [], upload);
|
|
2843
|
-
process.exit(exitCode);
|
|
1353
|
+
stopSpinner(true, `Found ${response.data.workflows.length} workflow(s)`);
|
|
1354
|
+
for (const item of response.data.workflows) printWorkflow(item);
|
|
2844
1355
|
});
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
}
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
};
|
|
2865
|
-
}
|
|
2866
|
-
function resolveWorkspaceId(options) {
|
|
2867
|
-
return normalizeWorkspaceId(options.workspace) ?? normalizeWorkspaceId(process.env.MOXT_WORKSPACE_ID);
|
|
2868
|
-
}
|
|
2869
|
-
function normalizeWorkspaceId(value) {
|
|
2870
|
-
const trimmed = value?.trim();
|
|
2871
|
-
return trimmed === void 0 || trimmed === "" ? void 0 : trimmed;
|
|
2872
|
-
}
|
|
2873
|
-
async function runLocalAgentOrExit(agent, agentArgs, upload) {
|
|
2874
|
-
return await runLocalAgent({
|
|
2875
|
-
agent,
|
|
2876
|
-
agentArgs,
|
|
2877
|
-
...upload === void 0 ? {} : { upload }
|
|
2878
|
-
});
|
|
2879
|
-
}
|
|
2880
|
-
|
|
2881
|
-
// src/telemetry/config.ts
|
|
2882
|
-
import * as fs2 from "fs";
|
|
2883
|
-
import * as os from "os";
|
|
2884
|
-
import * as path from "path";
|
|
2885
|
-
function getConfigPath() {
|
|
2886
|
-
return path.join(os.homedir(), ".moxt", "config.json");
|
|
2887
|
-
}
|
|
2888
|
-
function readTelemetryConfig() {
|
|
2889
|
-
try {
|
|
2890
|
-
const raw = fs2.readFileSync(getConfigPath(), "utf8");
|
|
2891
|
-
const parsed = JSON.parse(raw);
|
|
2892
|
-
return parsed.telemetry ?? {};
|
|
2893
|
-
} catch {
|
|
2894
|
-
return {};
|
|
2895
|
-
}
|
|
2896
|
-
}
|
|
2897
|
-
function writeTelemetryConfig(update) {
|
|
2898
|
-
const configPath = getConfigPath();
|
|
2899
|
-
let existing = {};
|
|
2900
|
-
try {
|
|
2901
|
-
existing = JSON.parse(fs2.readFileSync(configPath, "utf8"));
|
|
2902
|
-
} catch {
|
|
2903
|
-
existing = {};
|
|
2904
|
-
}
|
|
2905
|
-
const currentTelemetry = existing.telemetry ?? {};
|
|
2906
|
-
const next = { ...existing, telemetry: { ...currentTelemetry, ...update } };
|
|
2907
|
-
fs2.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
2908
|
-
fs2.writeFileSync(configPath, JSON.stringify(next, null, 2));
|
|
2909
|
-
}
|
|
2910
|
-
|
|
2911
|
-
// src/telemetry/opt-out.ts
|
|
2912
|
-
function isCiEnvironment() {
|
|
2913
|
-
return process.env.CI === "true" || process.env.CI === "1";
|
|
2914
|
-
}
|
|
2915
|
-
function isTelemetryDisabled() {
|
|
2916
|
-
if (process.env.MOXT_TELEMETRY_DISABLED === "1" || process.env.MOXT_TELEMETRY_DISABLED === "true") {
|
|
2917
|
-
return true;
|
|
2918
|
-
}
|
|
2919
|
-
if (process.env.DO_NOT_TRACK === "1" || process.env.DO_NOT_TRACK === "true") {
|
|
2920
|
-
return true;
|
|
2921
|
-
}
|
|
2922
|
-
return readTelemetryConfig().disabled === true;
|
|
2923
|
-
}
|
|
2924
|
-
|
|
2925
|
-
// src/commands/telemetry.ts
|
|
2926
|
-
function registerTelemetryCommand(program2) {
|
|
2927
|
-
const telemetry = program2.command("telemetry").description("Manage Moxt CLI telemetry");
|
|
2928
|
-
telemetry.command("status").description("Show telemetry status").action(() => {
|
|
2929
|
-
const cfg = readTelemetryConfig();
|
|
2930
|
-
const envOverride = process.env.MOXT_TELEMETRY_DISABLED === "1" || process.env.MOXT_TELEMETRY_DISABLED === "true" || process.env.DO_NOT_TRACK === "1" || process.env.DO_NOT_TRACK === "true";
|
|
2931
|
-
const state = isTelemetryDisabled() ? "disabled" : "enabled";
|
|
2932
|
-
print(`telemetry: ${state}`);
|
|
2933
|
-
if (envOverride) {
|
|
2934
|
-
print("(disabled via environment variable)");
|
|
2935
|
-
} else if (cfg.disabled) {
|
|
2936
|
-
print("(disabled via config file)");
|
|
1356
|
+
addSpaceOptions2(
|
|
1357
|
+
workflow.command("create").description("Create a blank workflow").requiredOption("-p, --path <path>", "Target .workflow path").option("--desc-file <localPath>", "Local UTF-8 text file for workflow description").option("--owner-human-id <humanId>", "Workflow owner human ID")
|
|
1358
|
+
).action(async (options) => {
|
|
1359
|
+
const body = runOrExit3(() => {
|
|
1360
|
+
const next = {
|
|
1361
|
+
path: options.path,
|
|
1362
|
+
...resolveSpaceSelector(options)
|
|
1363
|
+
};
|
|
1364
|
+
const desc = readDescFile(options.descFile);
|
|
1365
|
+
if (desc !== void 0) next.desc = desc;
|
|
1366
|
+
const ownerHumanId = parseOptionalPositiveInteger(options.ownerHumanId, "--owner-human-id");
|
|
1367
|
+
if (ownerHumanId !== void 0) next.ownerHumanId = ownerHumanId;
|
|
1368
|
+
return next;
|
|
1369
|
+
});
|
|
1370
|
+
startSpinner("Creating workflow...");
|
|
1371
|
+
const response = await httpPost(`/workspaces/${options.workspace}/workflows`, body);
|
|
1372
|
+
if (!response.ok) {
|
|
1373
|
+
stopSpinner(false, "Failed");
|
|
1374
|
+
failResponse(response);
|
|
2937
1375
|
}
|
|
1376
|
+
stopSpinner(true, "Workflow created");
|
|
1377
|
+
printWorkflow(response.data);
|
|
2938
1378
|
});
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
1379
|
+
addWorkspaceOption(workflow.command("get").description("Get a workflow").argument("<workflowFileId>")).action(
|
|
1380
|
+
async (workflowFileId, options) => {
|
|
1381
|
+
startSpinner("Fetching workflow...");
|
|
1382
|
+
const response = await httpGet(
|
|
1383
|
+
`/workspaces/${options.workspace}/workflows/${workflowFileId}`
|
|
1384
|
+
);
|
|
1385
|
+
if (!response.ok) {
|
|
1386
|
+
stopSpinner(false, "Failed");
|
|
1387
|
+
failResponse(response);
|
|
1388
|
+
}
|
|
1389
|
+
stopSpinner(true, "Workflow fetched");
|
|
1390
|
+
printWorkflowDetail(response.data);
|
|
1391
|
+
}
|
|
1392
|
+
);
|
|
1393
|
+
addWorkspaceOption(
|
|
1394
|
+
workflow.command("update").description("Update workflow metadata").argument("<workflowFileId>").option("--desc-file <localPath>", "Local UTF-8 text file for workflow description").option("--owner-human-id <humanId>", "Workflow owner human ID")
|
|
1395
|
+
).action(async (workflowFileId, options) => {
|
|
1396
|
+
const body = runOrExit3(() => {
|
|
1397
|
+
const next = {};
|
|
1398
|
+
const desc = readDescFile(options.descFile);
|
|
1399
|
+
if (desc !== void 0) next.desc = desc;
|
|
1400
|
+
const ownerHumanId = parseOptionalPositiveInteger(options.ownerHumanId, "--owner-human-id");
|
|
1401
|
+
if (ownerHumanId !== void 0) next.ownerHumanId = ownerHumanId;
|
|
1402
|
+
assertNonEmptyBody(next);
|
|
1403
|
+
return next;
|
|
1404
|
+
});
|
|
1405
|
+
startSpinner("Updating workflow...");
|
|
1406
|
+
const response = await httpPatch(`/workspaces/${options.workspace}/workflows/${workflowFileId}`, body);
|
|
1407
|
+
if (!response.ok) {
|
|
1408
|
+
stopSpinner(false, "Failed");
|
|
1409
|
+
failResponse(response);
|
|
1410
|
+
}
|
|
1411
|
+
stopSpinner(true, "Workflow updated");
|
|
1412
|
+
printWorkflow(response.data);
|
|
2942
1413
|
});
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
1414
|
+
addWorkspaceOption(
|
|
1415
|
+
workflow.command("delete").description("Delete a workflow").argument("<workflowFileId>").option("--message <message>", "Commit message")
|
|
1416
|
+
).action(async (workflowFileId, options) => {
|
|
1417
|
+
startSpinner("Deleting workflow...");
|
|
1418
|
+
const response = await httpDelete(
|
|
1419
|
+
`/workspaces/${options.workspace}/workflows/${workflowFileId}`,
|
|
1420
|
+
options.message ? { message: options.message } : {}
|
|
1421
|
+
);
|
|
1422
|
+
if (!response.ok) {
|
|
1423
|
+
stopSpinner(false, "Failed");
|
|
1424
|
+
failResponse(response);
|
|
1425
|
+
}
|
|
1426
|
+
stopSpinner(true, `Deleted: ${response.data.fileId}`);
|
|
2946
1427
|
});
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
1428
|
+
addWorkspaceOption(workflow.command("duplicate").description("Duplicate a workflow").argument("<workflowFileId>")).action(
|
|
1429
|
+
async (workflowFileId, options) => {
|
|
1430
|
+
startSpinner("Duplicating workflow...");
|
|
1431
|
+
const response = await httpPost(
|
|
1432
|
+
`/workspaces/${options.workspace}/workflows/${workflowFileId}/duplicate`,
|
|
1433
|
+
{}
|
|
1434
|
+
);
|
|
1435
|
+
if (!response.ok) {
|
|
1436
|
+
stopSpinner(false, "Failed");
|
|
1437
|
+
failResponse(response);
|
|
1438
|
+
}
|
|
1439
|
+
stopSpinner(true, "Workflow duplicated");
|
|
1440
|
+
printWorkflow(response.data);
|
|
1441
|
+
}
|
|
1442
|
+
);
|
|
1443
|
+
registerStatusCommands(workflow);
|
|
1444
|
+
registerTransitionCommands(workflow);
|
|
1445
|
+
registerTaskCommands(workflow);
|
|
1446
|
+
registerCommentCommands(workflow);
|
|
1447
|
+
registerRunCommands(workflow);
|
|
1448
|
+
}
|
|
1449
|
+
function registerStatusCommands(workflow) {
|
|
1450
|
+
const status = workflow.command("status").description("Workflow status operations");
|
|
1451
|
+
addPreferredAssigneeOptions(
|
|
1452
|
+
addWorkspaceOption(
|
|
1453
|
+
status.command("create").description("Create a workflow status").argument("<workflowFileId>").requiredOption("--name <name>", "Status name").requiredOption("--type <type>", "Status type value: 1=todo, 2=in-progress, 3=done").option("--desc-file <localPath>", "Local UTF-8 text file for status description")
|
|
1454
|
+
)
|
|
1455
|
+
).action(
|
|
1456
|
+
async (workflowFileId, options) => {
|
|
1457
|
+
const body = runOrExit3(() => {
|
|
1458
|
+
const next = {
|
|
1459
|
+
name: options.name,
|
|
1460
|
+
type: parseRequiredStatusType(options.type)
|
|
1461
|
+
};
|
|
1462
|
+
const desc = readDescFile(options.descFile);
|
|
1463
|
+
if (desc !== void 0) next.desc = desc;
|
|
1464
|
+
Object.assign(next, parsePreferredAssigneeBody(options));
|
|
1465
|
+
return next;
|
|
1466
|
+
});
|
|
1467
|
+
startSpinner("Creating status...");
|
|
1468
|
+
const response = await httpPost(
|
|
1469
|
+
`/workspaces/${options.workspace}/workflows/${workflowFileId}/statuses`,
|
|
1470
|
+
body
|
|
1471
|
+
);
|
|
1472
|
+
if (!response.ok) {
|
|
1473
|
+
stopSpinner(false, "Failed");
|
|
1474
|
+
failResponse(response);
|
|
1475
|
+
}
|
|
1476
|
+
stopSpinner(true, "Status created");
|
|
1477
|
+
printStatusDetail(response.data);
|
|
1478
|
+
}
|
|
1479
|
+
);
|
|
1480
|
+
addPreferredAssigneeOptions(
|
|
1481
|
+
addWorkspaceOption(
|
|
1482
|
+
status.command("update").description("Update a workflow status").argument("<workflowFileId>").argument("<statusScopedId>").option("--name <name>", "Status name").option("--type <type>", "Status type value: 1=todo, 2=in-progress, 3=done").option("--desc-file <localPath>", "Local UTF-8 text file for status description").option("--clear-desc", "Clear status description").option("--clear-preferred-assignee", "Clear preferred assignee")
|
|
1483
|
+
)
|
|
1484
|
+
).action(
|
|
1485
|
+
async (workflowFileId, statusScopedId, options) => {
|
|
1486
|
+
const body = runOrExit3(() => {
|
|
1487
|
+
if (options.descFile && options.clearDesc) {
|
|
1488
|
+
throw new WorkflowOptionsError("Error: --desc-file cannot be used with --clear-desc.");
|
|
1489
|
+
}
|
|
1490
|
+
const next = {};
|
|
1491
|
+
if (options.name) next.name = options.name;
|
|
1492
|
+
const type = parseStatusType(options.type);
|
|
1493
|
+
if (type !== void 0) next.type = type;
|
|
1494
|
+
const desc = readDescFile(options.descFile);
|
|
1495
|
+
if (desc !== void 0) next.desc = desc;
|
|
1496
|
+
if (options.clearDesc) next.desc = null;
|
|
1497
|
+
Object.assign(next, parsePreferredAssigneeBody(options));
|
|
1498
|
+
assertNonEmptyBody(next);
|
|
1499
|
+
return next;
|
|
1500
|
+
});
|
|
1501
|
+
const id = runOrExit3(() => parsePositiveInteger(statusScopedId, "statusScopedId"));
|
|
1502
|
+
startSpinner("Updating status...");
|
|
1503
|
+
const response = await httpPatch(
|
|
1504
|
+
`/workspaces/${options.workspace}/workflows/${workflowFileId}/statuses/${id}`,
|
|
1505
|
+
body
|
|
1506
|
+
);
|
|
1507
|
+
if (!response.ok) {
|
|
1508
|
+
stopSpinner(false, "Failed");
|
|
1509
|
+
failResponse(response);
|
|
1510
|
+
}
|
|
1511
|
+
stopSpinner(true, "Status updated");
|
|
1512
|
+
printStatusDetail(response.data);
|
|
1513
|
+
}
|
|
1514
|
+
);
|
|
1515
|
+
addWorkspaceOption(
|
|
1516
|
+
status.command("move-after").description("Move a workflow status after another status").argument("<workflowFileId>").argument("<statusScopedId>").option("--after <statusScopedId>", "Anchor status scoped ID").option("--first", "Move to first position")
|
|
1517
|
+
).action(
|
|
1518
|
+
async (workflowFileId, statusScopedId, options) => {
|
|
1519
|
+
const { id, anchoredStatusScopedId } = runOrExit3(() => {
|
|
1520
|
+
if (options.after && options.first) {
|
|
1521
|
+
throw new WorkflowOptionsError("Error: --after cannot be used with --first.");
|
|
1522
|
+
}
|
|
1523
|
+
if (!options.after && !options.first) {
|
|
1524
|
+
throw new WorkflowOptionsError("Error: either --after or --first is required.");
|
|
1525
|
+
}
|
|
1526
|
+
return {
|
|
1527
|
+
id: parsePositiveInteger(statusScopedId, "statusScopedId"),
|
|
1528
|
+
anchoredStatusScopedId: options.first ? null : parsePositiveInteger(options.after, "--after")
|
|
1529
|
+
};
|
|
1530
|
+
});
|
|
1531
|
+
startSpinner("Moving status...");
|
|
1532
|
+
const response = await httpPost(
|
|
1533
|
+
`/workspaces/${options.workspace}/workflows/${workflowFileId}/statuses/${id}/move-after`,
|
|
1534
|
+
{ anchoredStatusScopedId }
|
|
1535
|
+
);
|
|
1536
|
+
if (!response.ok) {
|
|
1537
|
+
stopSpinner(false, "Failed");
|
|
1538
|
+
failResponse(response);
|
|
1539
|
+
}
|
|
1540
|
+
stopSpinner(true, "Status moved");
|
|
1541
|
+
}
|
|
1542
|
+
);
|
|
1543
|
+
addWorkspaceOption(
|
|
1544
|
+
status.command("delete").description("Delete a workflow status").argument("<workflowFileId>").argument("<statusScopedId>")
|
|
1545
|
+
).action(async (workflowFileId, statusScopedId, options) => {
|
|
1546
|
+
const id = runOrExit3(() => parsePositiveInteger(statusScopedId, "statusScopedId"));
|
|
1547
|
+
startSpinner("Deleting status...");
|
|
1548
|
+
const response = await httpDelete(
|
|
1549
|
+
`/workspaces/${options.workspace}/workflows/${workflowFileId}/statuses/${id}`
|
|
1550
|
+
);
|
|
1551
|
+
if (!response.ok) {
|
|
2959
1552
|
stopSpinner(false, "Failed");
|
|
2960
|
-
|
|
2961
|
-
process.exit(1);
|
|
1553
|
+
failResponse(response);
|
|
2962
1554
|
}
|
|
1555
|
+
stopSpinner(true, `Deleted: ${response.data.scopedId}`);
|
|
2963
1556
|
});
|
|
2964
1557
|
}
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
1558
|
+
function registerTransitionCommands(workflow) {
|
|
1559
|
+
const transition = workflow.command("transition").description("Workflow transition operations");
|
|
1560
|
+
for (const action of ["create", "delete"]) {
|
|
1561
|
+
addWorkspaceOption(
|
|
1562
|
+
transition.command(action).description(`${action === "create" ? "Create" : "Delete"} a workflow transition`).argument("<workflowFileId>").requiredOption("--from <statusScopedId>", "Source status scoped ID").requiredOption("--to <statusScopedId>", "Target status scoped ID")
|
|
1563
|
+
).action(async (workflowFileId, options) => {
|
|
1564
|
+
const body = runOrExit3(() => ({
|
|
1565
|
+
statusFromScopedId: parsePositiveInteger(options.from, "--from"),
|
|
1566
|
+
statusToScopedId: parsePositiveInteger(options.to, "--to")
|
|
1567
|
+
}));
|
|
1568
|
+
startSpinner(`${action === "create" ? "Creating" : "Deleting"} transition...`);
|
|
1569
|
+
const response = action === "create" ? await httpPost(
|
|
1570
|
+
`/workspaces/${options.workspace}/workflows/${workflowFileId}/transitions`,
|
|
1571
|
+
body
|
|
1572
|
+
) : await httpDelete(
|
|
1573
|
+
`/workspaces/${options.workspace}/workflows/${workflowFileId}/transitions`,
|
|
1574
|
+
body
|
|
1575
|
+
);
|
|
1576
|
+
if (!response.ok) {
|
|
1577
|
+
stopSpinner(false, "Failed");
|
|
1578
|
+
failResponse(response);
|
|
1579
|
+
}
|
|
1580
|
+
stopSpinner(true, action === "create" ? "Transition created" : "Transition deleted");
|
|
1581
|
+
print(`${response.data.statusFromScopedId} -> ${response.data.statusToScopedId}`);
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
2970
1584
|
}
|
|
2971
|
-
function
|
|
2972
|
-
const
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
const
|
|
2978
|
-
|
|
2979
|
-
|
|
1585
|
+
function registerTaskCommands(workflow) {
|
|
1586
|
+
const task = workflow.command("task").description("Workflow task operations");
|
|
1587
|
+
addWorkspaceOption(
|
|
1588
|
+
task.command("list").description("List workflow tasks").option("--workflow-file-id <workflowFileId>", "Filter by workflow file ID").option("--scope <scope>", "Task scope", "all").option("-l, --limit <limit>", "Maximum number of tasks to return", "20").option("--cursor <cursor>", "Pagination cursor")
|
|
1589
|
+
).action(
|
|
1590
|
+
async (options) => {
|
|
1591
|
+
const query = runOrExit3(() => buildQuery({
|
|
1592
|
+
scope: parseTaskScope(options.scope),
|
|
1593
|
+
limit: parseSearchLimit(options.limit, 20, 100),
|
|
1594
|
+
cursor: options.cursor,
|
|
1595
|
+
workflowFileId: options.workflowFileId
|
|
1596
|
+
}));
|
|
1597
|
+
startSpinner("Fetching workflow tasks...");
|
|
1598
|
+
const response = await httpGet(`/workspaces/${options.workspace}/workflow-tasks${query}`);
|
|
1599
|
+
if (!response.ok) {
|
|
1600
|
+
stopSpinner(false, "Failed");
|
|
1601
|
+
failResponse(response);
|
|
1602
|
+
}
|
|
1603
|
+
if (response.data.tasks.length === 0) {
|
|
1604
|
+
stopSpinner(true, "No workflow tasks found.");
|
|
2980
1605
|
return;
|
|
2981
1606
|
}
|
|
2982
|
-
stopSpinner(true, `Found ${
|
|
2983
|
-
for (const
|
|
2984
|
-
|
|
1607
|
+
stopSpinner(true, `Found ${response.data.tasks.length} task(s)`);
|
|
1608
|
+
for (const item of response.data.tasks) printTaskSummary(item);
|
|
1609
|
+
if (response.data.nextCursor) print(`${colors.dim}nextCursor=${response.data.nextCursor}${colors.reset}`);
|
|
1610
|
+
}
|
|
1611
|
+
);
|
|
1612
|
+
addWorkspaceOption(
|
|
1613
|
+
task.command("get").description("Get a workflow task").argument("<workflowFileId>").argument("<taskScopedId>")
|
|
1614
|
+
).action(async (workflowFileId, taskScopedId, options) => {
|
|
1615
|
+
const id = runOrExit3(() => parsePositiveInteger(taskScopedId, "taskScopedId"));
|
|
1616
|
+
startSpinner("Fetching task...");
|
|
1617
|
+
const response = await httpGet(
|
|
1618
|
+
`/workspaces/${options.workspace}/workflows/${workflowFileId}/tasks/${id}`
|
|
1619
|
+
);
|
|
1620
|
+
if (!response.ok) {
|
|
1621
|
+
stopSpinner(false, "Failed");
|
|
1622
|
+
failResponse(response);
|
|
1623
|
+
}
|
|
1624
|
+
stopSpinner(true, "Task fetched");
|
|
1625
|
+
printTaskDetail(response.data);
|
|
1626
|
+
});
|
|
1627
|
+
addAssigneeOptions(
|
|
1628
|
+
addWorkspaceOption(
|
|
1629
|
+
task.command("create").description("Create a workflow task").argument("<workflowFileId>").requiredOption("--title <title>", "Task title").requiredOption("--status <statusScopedId>", "Status scoped ID").option("--priority <priority>", "Priority: urgent, high, medium, or low", "medium").option("--desc-file <localPath>", "Local UTF-8 text file for task description").option("--owner-human-id <humanId>", "Task owner human ID")
|
|
1630
|
+
)
|
|
1631
|
+
).action(
|
|
1632
|
+
async (workflowFileId, options) => {
|
|
1633
|
+
const body = runOrExit3(() => {
|
|
1634
|
+
const next = {
|
|
1635
|
+
title: options.title,
|
|
1636
|
+
statusScopedId: parsePositiveInteger(options.status, "--status"),
|
|
1637
|
+
priority: parseRequiredPriority(options.priority)
|
|
1638
|
+
};
|
|
1639
|
+
const desc = readDescFile(options.descFile);
|
|
1640
|
+
if (desc !== void 0) next.desc = desc;
|
|
1641
|
+
const ownerHumanId = parseOptionalPositiveInteger(options.ownerHumanId, "--owner-human-id");
|
|
1642
|
+
if (ownerHumanId !== void 0) next.ownerHumanId = ownerHumanId;
|
|
1643
|
+
Object.assign(next, parseAssigneeBody(options, false));
|
|
1644
|
+
return next;
|
|
1645
|
+
});
|
|
1646
|
+
startSpinner("Creating task...");
|
|
1647
|
+
const response = await httpPost(
|
|
1648
|
+
`/workspaces/${options.workspace}/workflows/${workflowFileId}/tasks`,
|
|
1649
|
+
body
|
|
1650
|
+
);
|
|
1651
|
+
if (!response.ok) {
|
|
1652
|
+
stopSpinner(false, "Failed");
|
|
1653
|
+
failResponse(response);
|
|
2985
1654
|
}
|
|
2986
|
-
|
|
1655
|
+
stopSpinner(true, "Task created");
|
|
1656
|
+
printTaskDetail(response.data);
|
|
1657
|
+
}
|
|
1658
|
+
);
|
|
1659
|
+
addWorkspaceOption(
|
|
1660
|
+
task.command("update").description("Update workflow task metadata").argument("<workflowFileId>").argument("<taskScopedId>").option("--title <title>", "Task title").option("--priority <priority>", "Priority: urgent, high, medium, or low").option("--desc-file <localPath>", "Local UTF-8 text file for task description").option("--clear-desc", "Clear task description").option("--owner-human-id <humanId>", "Task owner human ID")
|
|
1661
|
+
).action(
|
|
1662
|
+
async (workflowFileId, taskScopedId, options) => {
|
|
1663
|
+
const body = runOrExit3(() => {
|
|
1664
|
+
if (options.descFile && options.clearDesc) {
|
|
1665
|
+
throw new WorkflowOptionsError("Error: --desc-file cannot be used with --clear-desc.");
|
|
1666
|
+
}
|
|
1667
|
+
const next = {};
|
|
1668
|
+
if (options.title) next.title = options.title;
|
|
1669
|
+
const priority = parsePriority(options.priority);
|
|
1670
|
+
if (priority !== void 0) next.priority = priority;
|
|
1671
|
+
const desc = readDescFile(options.descFile);
|
|
1672
|
+
if (desc !== void 0) next.desc = desc;
|
|
1673
|
+
if (options.clearDesc) next.desc = null;
|
|
1674
|
+
const ownerHumanId = parseOptionalPositiveInteger(options.ownerHumanId, "--owner-human-id");
|
|
1675
|
+
if (ownerHumanId !== void 0) next.ownerHumanId = ownerHumanId;
|
|
1676
|
+
assertNonEmptyBody(next);
|
|
1677
|
+
return next;
|
|
1678
|
+
});
|
|
1679
|
+
const id = runOrExit3(() => parsePositiveInteger(taskScopedId, "taskScopedId"));
|
|
1680
|
+
startSpinner("Updating task...");
|
|
1681
|
+
const response = await httpPatch(
|
|
1682
|
+
`/workspaces/${options.workspace}/workflows/${workflowFileId}/tasks/${id}/metadata`,
|
|
1683
|
+
body
|
|
1684
|
+
);
|
|
1685
|
+
if (!response.ok) {
|
|
1686
|
+
stopSpinner(false, "Failed");
|
|
1687
|
+
failResponse(response);
|
|
1688
|
+
}
|
|
1689
|
+
stopSpinner(true, "Task updated");
|
|
1690
|
+
printTaskDetail(response.data);
|
|
1691
|
+
}
|
|
1692
|
+
);
|
|
1693
|
+
for (const action of ["add-linked-files", "remove-linked-files"]) {
|
|
1694
|
+
addWorkspaceOption(
|
|
1695
|
+
task.command(action).description(`${action === "add-linked-files" ? "Add" : "Remove"} linked files on a workflow task`).argument("<workflowFileId>").argument("<taskScopedId>").requiredOption("--file-ids <ids>", "Comma-separated file IDs")
|
|
1696
|
+
).action(
|
|
1697
|
+
async (workflowFileId, taskScopedId, options) => {
|
|
1698
|
+
const { id, fileIds } = runOrExit3(() => ({
|
|
1699
|
+
id: parsePositiveInteger(taskScopedId, "taskScopedId"),
|
|
1700
|
+
fileIds: parseFileIds(options.fileIds, "--file-ids")
|
|
1701
|
+
}));
|
|
1702
|
+
startSpinner(action === "add-linked-files" ? "Adding linked files..." : "Removing linked files...");
|
|
1703
|
+
const path2 = `/workspaces/${options.workspace}/workflows/${workflowFileId}/tasks/${id}/linked-files`;
|
|
1704
|
+
const response = action === "add-linked-files" ? await httpPost(path2, { fileIds }) : await httpDelete(path2, { fileIds });
|
|
1705
|
+
if (!response.ok) {
|
|
1706
|
+
stopSpinner(false, "Failed");
|
|
1707
|
+
failResponse(response);
|
|
1708
|
+
}
|
|
1709
|
+
stopSpinner(true, action === "add-linked-files" ? "Linked files added" : "Linked files removed");
|
|
1710
|
+
printTaskDetail(response.data);
|
|
1711
|
+
}
|
|
1712
|
+
);
|
|
1713
|
+
}
|
|
1714
|
+
addWorkspaceOption(
|
|
1715
|
+
task.command("move").description("Move a workflow task to another status").argument("<workflowFileId>").argument("<taskScopedId>").requiredOption("--status <statusScopedId>", "Target status scoped ID")
|
|
1716
|
+
).action(
|
|
1717
|
+
async (workflowFileId, taskScopedId, options) => {
|
|
1718
|
+
const { id, statusScopedId } = runOrExit3(() => ({
|
|
1719
|
+
id: parsePositiveInteger(taskScopedId, "taskScopedId"),
|
|
1720
|
+
statusScopedId: parsePositiveInteger(options.status, "--status")
|
|
1721
|
+
}));
|
|
1722
|
+
startSpinner("Moving task...");
|
|
1723
|
+
const response = await httpPatch(
|
|
1724
|
+
`/workspaces/${options.workspace}/workflows/${workflowFileId}/tasks/${id}/status`,
|
|
1725
|
+
{ statusScopedId }
|
|
1726
|
+
);
|
|
1727
|
+
if (!response.ok) {
|
|
1728
|
+
stopSpinner(false, "Failed");
|
|
1729
|
+
failResponse(response);
|
|
1730
|
+
}
|
|
1731
|
+
stopSpinner(true, "Task moved");
|
|
1732
|
+
printTaskDetail(response.data);
|
|
1733
|
+
}
|
|
1734
|
+
);
|
|
1735
|
+
addAssigneeOptions(
|
|
1736
|
+
addWorkspaceOption(
|
|
1737
|
+
task.command("assign").description("Update workflow task assignee").argument("<workflowFileId>").argument("<taskScopedId>").option("--clear", "Clear assignee")
|
|
1738
|
+
)
|
|
1739
|
+
).action(
|
|
1740
|
+
async (workflowFileId, taskScopedId, options) => {
|
|
1741
|
+
const body = runOrExit3(() => {
|
|
1742
|
+
const next = parseAssigneeBody(options, true);
|
|
1743
|
+
if (Object.keys(next).length === 0 && options.clear !== true) assertNonEmptyBody(next);
|
|
1744
|
+
return next;
|
|
1745
|
+
});
|
|
1746
|
+
const id = runOrExit3(() => parsePositiveInteger(taskScopedId, "taskScopedId"));
|
|
1747
|
+
startSpinner("Updating task assignee...");
|
|
1748
|
+
const response = await httpPatch(
|
|
1749
|
+
`/workspaces/${options.workspace}/workflows/${workflowFileId}/tasks/${id}/assignee`,
|
|
1750
|
+
body
|
|
1751
|
+
);
|
|
1752
|
+
if (!response.ok) {
|
|
1753
|
+
stopSpinner(false, "Failed");
|
|
1754
|
+
failResponse(response);
|
|
1755
|
+
}
|
|
1756
|
+
stopSpinner(true, "Task assignee updated");
|
|
1757
|
+
printTaskDetail(response.data);
|
|
1758
|
+
}
|
|
1759
|
+
);
|
|
1760
|
+
addWorkspaceOption(
|
|
1761
|
+
task.command("delete").description("Delete a workflow task").argument("<workflowFileId>").argument("<taskScopedId>")
|
|
1762
|
+
).action(async (workflowFileId, taskScopedId, options) => {
|
|
1763
|
+
const id = runOrExit3(() => parsePositiveInteger(taskScopedId, "taskScopedId"));
|
|
1764
|
+
startSpinner("Deleting task...");
|
|
1765
|
+
const response = await httpDelete(
|
|
1766
|
+
`/workspaces/${options.workspace}/workflows/${workflowFileId}/tasks/${id}`
|
|
1767
|
+
);
|
|
1768
|
+
if (!response.ok) {
|
|
2987
1769
|
stopSpinner(false, "Failed");
|
|
2988
|
-
|
|
2989
|
-
process.exit(1);
|
|
1770
|
+
failResponse(response);
|
|
2990
1771
|
}
|
|
1772
|
+
stopSpinner(true, `Deleted: ${response.data.scopedId}`);
|
|
2991
1773
|
});
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
1774
|
+
}
|
|
1775
|
+
function registerCommentCommands(workflow) {
|
|
1776
|
+
const comment = workflow.command("comment").description("Workflow task comment operations");
|
|
1777
|
+
addWorkspaceOption(
|
|
1778
|
+
comment.command("create").description("Create a workflow task comment").argument("<workflowFileId>").argument("<taskScopedId>").requiredOption("--content-path <localPath>", "Local UTF-8 text file for comment content")
|
|
1779
|
+
).action(
|
|
1780
|
+
async (workflowFileId, taskScopedId, options) => {
|
|
1781
|
+
const { id, content } = runOrExit3(() => ({
|
|
1782
|
+
id: parsePositiveInteger(taskScopedId, "taskScopedId"),
|
|
1783
|
+
content: readCommentContentFile(options.contentPath)
|
|
1784
|
+
}));
|
|
1785
|
+
startSpinner("Creating comment...");
|
|
1786
|
+
const response = await httpPost(
|
|
1787
|
+
`/workspaces/${options.workspace}/workflows/${workflowFileId}/tasks/${id}/comments`,
|
|
1788
|
+
{ content }
|
|
1789
|
+
);
|
|
1790
|
+
if (!response.ok) {
|
|
1791
|
+
stopSpinner(false, "Failed");
|
|
1792
|
+
failResponse(response);
|
|
1793
|
+
}
|
|
1794
|
+
stopSpinner(true, "Comment created");
|
|
1795
|
+
printCommentDetail(response.data);
|
|
1796
|
+
}
|
|
1797
|
+
);
|
|
1798
|
+
addWorkspaceOption(
|
|
1799
|
+
comment.command("list").description("List workflow task comments").argument("<workflowFileId>").argument("<taskScopedId>")
|
|
1800
|
+
).action(async (workflowFileId, taskScopedId, options) => {
|
|
1801
|
+
const id = runOrExit3(() => parsePositiveInteger(taskScopedId, "taskScopedId"));
|
|
1802
|
+
startSpinner("Fetching comments...");
|
|
1803
|
+
const response = await httpGet(
|
|
1804
|
+
`/workspaces/${options.workspace}/workflows/${workflowFileId}/tasks/${id}/comments`
|
|
1805
|
+
);
|
|
2997
1806
|
if (!response.ok) {
|
|
2998
|
-
stopSpinner(false, "Failed
|
|
2999
|
-
|
|
3000
|
-
process.exit(1);
|
|
1807
|
+
stopSpinner(false, "Failed");
|
|
1808
|
+
failResponse(response);
|
|
3001
1809
|
}
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
stopSpinner(true, "No members found.");
|
|
1810
|
+
if (response.data.items.length === 0) {
|
|
1811
|
+
stopSpinner(true, "No comments found.");
|
|
3005
1812
|
return;
|
|
3006
1813
|
}
|
|
3007
|
-
stopSpinner(true, `Found ${
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
}
|
|
1814
|
+
stopSpinner(true, `Found ${response.data.items.length} comment(s)`);
|
|
1815
|
+
response.data.items.forEach((item, index) => {
|
|
1816
|
+
if (index > 0) print("");
|
|
1817
|
+
printCommentDetail(item);
|
|
1818
|
+
});
|
|
3012
1819
|
});
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
1820
|
+
addWorkspaceOption(
|
|
1821
|
+
comment.command("delete").description("Delete a workflow task comment").argument("<workflowFileId>").argument("<taskScopedId>").argument("<commentId>")
|
|
1822
|
+
).action(
|
|
1823
|
+
async (workflowFileId, taskScopedId, commentId, options) => {
|
|
1824
|
+
const ids = runOrExit3(() => ({
|
|
1825
|
+
task: parsePositiveInteger(taskScopedId, "taskScopedId"),
|
|
1826
|
+
comment: parsePositiveInteger(commentId, "commentId")
|
|
1827
|
+
}));
|
|
1828
|
+
startSpinner("Deleting comment...");
|
|
1829
|
+
const response = await httpDelete(
|
|
1830
|
+
`/workspaces/${options.workspace}/workflows/${workflowFileId}/tasks/${ids.task}/comments/${ids.comment}`
|
|
1831
|
+
);
|
|
1832
|
+
if (!response.ok) {
|
|
1833
|
+
stopSpinner(false, "Failed");
|
|
1834
|
+
failResponse(response);
|
|
3024
1835
|
}
|
|
3025
|
-
|
|
1836
|
+
stopSpinner(true, `Deleted: ${response.data.id}`);
|
|
3026
1837
|
}
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
1838
|
+
);
|
|
1839
|
+
}
|
|
1840
|
+
function registerRunCommands(workflow) {
|
|
1841
|
+
const run = workflow.command("run").description("Workflow agent run operations");
|
|
1842
|
+
addWorkspaceOption(
|
|
1843
|
+
run.command("status").description("Get workflow agent run statuses").argument("<workflowFileId>").argument("<triggerIds...>")
|
|
1844
|
+
).action(async (workflowFileId, triggerIds, options) => {
|
|
1845
|
+
const workflowPipelineTriggerIds = runOrExit3(
|
|
1846
|
+
() => triggerIds.map((triggerId) => parsePositiveInteger(triggerId, "workflowPipelineTriggerId"))
|
|
1847
|
+
);
|
|
1848
|
+
startSpinner("Fetching run statuses...");
|
|
1849
|
+
const response = await httpPost(
|
|
1850
|
+
`/workspaces/${options.workspace}/workflows/${workflowFileId}/pipeline/triggers/statuses`,
|
|
1851
|
+
{ workflowPipelineTriggerIds }
|
|
1852
|
+
);
|
|
1853
|
+
if (!response.ok) {
|
|
1854
|
+
stopSpinner(false, "Failed");
|
|
1855
|
+
failResponse(response);
|
|
3033
1856
|
}
|
|
3034
|
-
stopSpinner(true,
|
|
3035
|
-
const
|
|
3036
|
-
|
|
3037
|
-
for (const email of emails) {
|
|
3038
|
-
const member = emailToMember.get(email.toLowerCase());
|
|
3039
|
-
if (!member) {
|
|
3040
|
-
print(`${colors.red}\u2717${colors.reset} ${email} - not found in workspace`);
|
|
3041
|
-
continue;
|
|
3042
|
-
}
|
|
3043
|
-
const deleteResponse = await httpDelete(`/workspaces/${workspaceId}/members/${member.userId}`);
|
|
3044
|
-
if (deleteResponse.ok) {
|
|
3045
|
-
print(`${colors.green}\u2713${colors.reset} ${email} - removed`);
|
|
3046
|
-
} else {
|
|
3047
|
-
print(`${colors.red}\u2717${colors.reset} ${email} - failed: ${JSON.stringify(deleteResponse.data)}`);
|
|
3048
|
-
}
|
|
1857
|
+
stopSpinner(true, `Found ${response.data.length} run status item(s)`);
|
|
1858
|
+
for (const item of response.data) {
|
|
1859
|
+
print(`${colors.bold}${item.workflowPipelineTriggerId}${colors.reset} ${item.pipelineStatus}`);
|
|
3049
1860
|
}
|
|
3050
1861
|
});
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
1862
|
+
addWorkspaceOption(
|
|
1863
|
+
run.command("output").description("Get workflow agent run output events").argument("<workflowFileId>").argument("<triggerId>")
|
|
1864
|
+
).action(async (workflowFileId, triggerId, options) => {
|
|
1865
|
+
const id = runOrExit3(() => parsePositiveInteger(triggerId, "workflowPipelineTriggerId"));
|
|
1866
|
+
startSpinner("Fetching run output...");
|
|
1867
|
+
const response = await httpGet(
|
|
1868
|
+
`/workspaces/${options.workspace}/workflows/${workflowFileId}/pipeline/trigger/${id}/output`
|
|
1869
|
+
);
|
|
3056
1870
|
if (!response.ok) {
|
|
3057
|
-
stopSpinner(false, "Failed
|
|
3058
|
-
|
|
3059
|
-
process.exit(1);
|
|
3060
|
-
}
|
|
3061
|
-
const { spaces } = response.data;
|
|
3062
|
-
if (spaces.length === 0) {
|
|
3063
|
-
stopSpinner(true, "No spaces found.");
|
|
3064
|
-
return;
|
|
1871
|
+
stopSpinner(false, "Failed");
|
|
1872
|
+
failResponse(response);
|
|
3065
1873
|
}
|
|
3066
|
-
stopSpinner(true, `
|
|
3067
|
-
print(
|
|
3068
|
-
|
|
1874
|
+
stopSpinner(true, `Run ${response.data.workflowPipelineTriggerId}`);
|
|
1875
|
+
for (const event of response.data.pipelineEvents) print(event);
|
|
1876
|
+
});
|
|
1877
|
+
addWorkspaceOption(
|
|
1878
|
+
run.command("abort").description("Abort a workflow agent run").argument("<workflowFileId>").argument("<triggerId>")
|
|
1879
|
+
).action(async (workflowFileId, triggerId, options) => {
|
|
1880
|
+
const id = runOrExit3(() => parsePositiveInteger(triggerId, "workflowPipelineTriggerId"));
|
|
1881
|
+
startSpinner("Aborting run...");
|
|
1882
|
+
const response = await httpPost(
|
|
1883
|
+
`/workspaces/${options.workspace}/workflows/${workflowFileId}/pipeline/trigger/${id}/abort`,
|
|
1884
|
+
{}
|
|
3069
1885
|
);
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
print(`${s.type} ${teamSpaceId} ${teammateId} ${s.name}`);
|
|
1886
|
+
if (!response.ok) {
|
|
1887
|
+
stopSpinner(false, "Failed");
|
|
1888
|
+
failResponse(response);
|
|
3074
1889
|
}
|
|
1890
|
+
stopSpinner(true, `Abort accepted: ${id}`);
|
|
3075
1891
|
});
|
|
3076
1892
|
}
|
|
3077
1893
|
|
|
@@ -3085,23 +1901,23 @@ function createProgram(version) {
|
|
|
3085
1901
|
registerWorkspaceCommand(program2);
|
|
3086
1902
|
registerFileCommand(program2);
|
|
3087
1903
|
registerMemoryCommand(program2);
|
|
3088
|
-
|
|
1904
|
+
registerWorkflowCommand(program2);
|
|
3089
1905
|
registerMiniappCommand(program2);
|
|
3090
1906
|
registerTelemetryCommand(program2);
|
|
3091
1907
|
return program2;
|
|
3092
1908
|
}
|
|
3093
1909
|
|
|
3094
1910
|
// src/telemetry/client.ts
|
|
3095
|
-
import { arch as
|
|
1911
|
+
import { arch as arch2, platform as platform2 } from "os";
|
|
3096
1912
|
var buffer = [];
|
|
3097
1913
|
var FLUSH_TIMEOUT_MS = 3e3;
|
|
3098
1914
|
var MAX_BATCH_SIZE = 100;
|
|
3099
1915
|
function commonLabels() {
|
|
3100
1916
|
return {
|
|
3101
|
-
cli_version: "0.3.3
|
|
1917
|
+
cli_version: "0.3.3",
|
|
3102
1918
|
node_version: process.versions.node,
|
|
3103
|
-
os:
|
|
3104
|
-
arch:
|
|
1919
|
+
os: platform2(),
|
|
1920
|
+
arch: arch2(),
|
|
3105
1921
|
is_ci: isCiEnvironment() ? "true" : "false"
|
|
3106
1922
|
};
|
|
3107
1923
|
}
|
|
@@ -3144,7 +1960,7 @@ async function flush() {
|
|
|
3144
1960
|
}
|
|
3145
1961
|
const batch = buffer.splice(0, MAX_BATCH_SIZE);
|
|
3146
1962
|
const payload = {
|
|
3147
|
-
release_id: "0.3.3
|
|
1963
|
+
release_id: "0.3.3",
|
|
3148
1964
|
client_type: "cli",
|
|
3149
1965
|
metric_data_list: batch.map((m) => ({
|
|
3150
1966
|
profile: "cli",
|
|
@@ -3285,9 +2101,9 @@ function installExitHandler() {
|
|
|
3285
2101
|
|
|
3286
2102
|
// src/index.ts
|
|
3287
2103
|
updateNotifier({
|
|
3288
|
-
pkg: { name: "@moxt-ai/cli", version: "0.3.3
|
|
2104
|
+
pkg: { name: "@moxt-ai/cli", version: "0.3.3" }
|
|
3289
2105
|
}).notify();
|
|
3290
|
-
var program = createProgram("0.3.3
|
|
2106
|
+
var program = createProgram("0.3.3");
|
|
3291
2107
|
instrumentProgram(program);
|
|
3292
2108
|
installExitHandler();
|
|
3293
2109
|
program.parseAsync(process.argv).then(async () => {
|