@agentsky/sky 2.9.1 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-YN7PUNIB.js → chunk-5QRKU3BR.js} +154 -72
- package/dist/index.js +400 -67
- package/dist/products.d.ts +28 -3
- package/dist/products.js +1 -1
- package/dist/{src-QFDLKCFY.js → src-RYECJN5I.js} +23 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
skillRefSchema,
|
|
17
17
|
splitMcpServers,
|
|
18
18
|
supersededBy
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-5QRKU3BR.js";
|
|
20
20
|
|
|
21
21
|
// src/index.ts
|
|
22
22
|
import { existsSync as existsSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
|
|
@@ -60,11 +60,11 @@ function apiToken() {
|
|
|
60
60
|
return process.env.SKY_API_TOKEN ?? loadConfig().token;
|
|
61
61
|
}
|
|
62
62
|
function currentUniverse() {
|
|
63
|
-
return process.env.SKY_UNIVERSE ?? loadConfig().currentUniverse;
|
|
63
|
+
return process.env.SKY_WORKSPACE ?? process.env.SKY_UNIVERSE ?? loadConfig().currentUniverse;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
// src/version.ts
|
|
67
|
-
var CLI_VERSION = "2.
|
|
67
|
+
var CLI_VERSION = "2.10.0";
|
|
68
68
|
var USER_AGENT = `sky-cli/${CLI_VERSION} (${process.platform}; node/${process.versions.node})`;
|
|
69
69
|
|
|
70
70
|
// src/api.ts
|
|
@@ -185,7 +185,7 @@ async function resolveSessionRef(ref) {
|
|
|
185
185
|
return agent2.defaultSessionId;
|
|
186
186
|
}
|
|
187
187
|
async function* streamEvents(sessionId, signal) {
|
|
188
|
-
const { parseSseStream } = await import("./src-
|
|
188
|
+
const { parseSseStream } = await import("./src-RYECJN5I.js");
|
|
189
189
|
const res = await fetch(
|
|
190
190
|
`${apiBase()}/sessions/${encodeURIComponent(sessionId)}/stream`,
|
|
191
191
|
{
|
|
@@ -559,7 +559,19 @@ function readClaudeCode() {
|
|
|
559
559
|
}
|
|
560
560
|
}
|
|
561
561
|
const { prompt, truncated } = assemblePrompt(parts);
|
|
562
|
-
const
|
|
562
|
+
const allServers = claudeMcpServers();
|
|
563
|
+
const { kept, dropped } = splitMcpServers(allServers);
|
|
564
|
+
const secretCandidates = [];
|
|
565
|
+
for (const d of dropped) {
|
|
566
|
+
const def = allServers[d.name];
|
|
567
|
+
if (!def?.env) continue;
|
|
568
|
+
for (const key of d.envKeys) {
|
|
569
|
+
const value = def.env[key];
|
|
570
|
+
if (value !== void 0 && value.trim()) {
|
|
571
|
+
secretCandidates.push({ serverName: d.name, envKey: key, value, url: d.url });
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
563
575
|
return {
|
|
564
576
|
harness: "claude_code",
|
|
565
577
|
configRoot,
|
|
@@ -568,7 +580,8 @@ function readClaudeCode() {
|
|
|
568
580
|
truncated,
|
|
569
581
|
model: claudeModel(configRoot),
|
|
570
582
|
keptServers: kept,
|
|
571
|
-
droppedServers: dropped
|
|
583
|
+
droppedServers: dropped,
|
|
584
|
+
secretCandidates
|
|
572
585
|
};
|
|
573
586
|
}
|
|
574
587
|
function detectHarness(explicit) {
|
|
@@ -602,6 +615,79 @@ function writeLink(configRoot, link) {
|
|
|
602
615
|
writeFileSync3(linkPath(configRoot), `${JSON.stringify(link, null, 2)}
|
|
603
616
|
`);
|
|
604
617
|
}
|
|
618
|
+
var DEFAULT_VAULT_NAME = "default";
|
|
619
|
+
async function fetchDefaultVaultId() {
|
|
620
|
+
const { vaults } = await api(
|
|
621
|
+
"GET",
|
|
622
|
+
"/vaults"
|
|
623
|
+
);
|
|
624
|
+
return vaults.find((v) => v.display_name === DEFAULT_VAULT_NAME)?.id ?? null;
|
|
625
|
+
}
|
|
626
|
+
async function ensureDefaultVaultId() {
|
|
627
|
+
const existing = await fetchDefaultVaultId();
|
|
628
|
+
if (existing) return existing;
|
|
629
|
+
try {
|
|
630
|
+
const { vault } = await api("POST", "/vaults", {
|
|
631
|
+
display_name: DEFAULT_VAULT_NAME
|
|
632
|
+
});
|
|
633
|
+
return vault.id;
|
|
634
|
+
} catch (error) {
|
|
635
|
+
if (error instanceof ApiError && error.status === 409) {
|
|
636
|
+
const raced = await fetchDefaultVaultId();
|
|
637
|
+
if (raced) return raced;
|
|
638
|
+
}
|
|
639
|
+
throw error;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
async function confirmAndVaultSecrets(candidates, yes) {
|
|
643
|
+
if (!candidates.length) return;
|
|
644
|
+
if (yes) {
|
|
645
|
+
console.log(
|
|
646
|
+
` ${candidates.length} secret${candidates.length === 1 ? "" : "s"} from dropped MCP servers not saved (--yes skips vault confirmation).`
|
|
647
|
+
);
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
console.log("\nSecret-bearing MCP servers were left out of the clone payload.");
|
|
651
|
+
console.log("You can save their keys to your workspace vault now (values are sent encrypted, never displayed).\n");
|
|
652
|
+
let vaultId;
|
|
653
|
+
let saved = 0;
|
|
654
|
+
let skipped = 0;
|
|
655
|
+
for (const c of candidates) {
|
|
656
|
+
const chosen = await confirm(` Add ${c.envKey} (from ${c.serverName}) to your default vault?`);
|
|
657
|
+
if (!chosen) {
|
|
658
|
+
console.log(` \u2014 skipped ${c.envKey}`);
|
|
659
|
+
skipped += 1;
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
try {
|
|
663
|
+
vaultId ??= await ensureDefaultVaultId();
|
|
664
|
+
await api("POST", `/vaults/${encodeURIComponent(vaultId)}/secrets`, {
|
|
665
|
+
display_name: `${c.envKey} (${c.serverName})`,
|
|
666
|
+
auth: {
|
|
667
|
+
type: "environment_variable",
|
|
668
|
+
secret_name: c.envKey,
|
|
669
|
+
value: c.value,
|
|
670
|
+
allowed_hosts: c.url ? [c.url] : []
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
console.log(` \u2713 saved ${c.envKey}`);
|
|
674
|
+
saved += 1;
|
|
675
|
+
} catch (error) {
|
|
676
|
+
if (error instanceof ApiError && error.status === 409) {
|
|
677
|
+
console.log(` \u2014 ${c.envKey} already in vault`);
|
|
678
|
+
} else {
|
|
679
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
680
|
+
console.log(` \u2717 ${c.envKey} failed: ${msg}`);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
if (saved || skipped) {
|
|
685
|
+
console.log(
|
|
686
|
+
`
|
|
687
|
+
Vault: ${saved} saved, ${skipped} skipped. Attach the "default" vault to a session to use these keys.`
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
605
691
|
function confirm(question) {
|
|
606
692
|
return new Promise((done) => {
|
|
607
693
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -633,7 +719,7 @@ async function createClone(local, opts) {
|
|
|
633
719
|
if (local.truncated) console.log(" note: instructions exceeded 100k chars and were truncated");
|
|
634
720
|
if (local.droppedServers.length) {
|
|
635
721
|
console.log(
|
|
636
|
-
`Left out
|
|
722
|
+
`Left out of clone (secret-bearing): ${local.droppedServers.map((d) => `${d.name} MCP`).join(", ")}`
|
|
637
723
|
);
|
|
638
724
|
}
|
|
639
725
|
if (opts.dryRun) {
|
|
@@ -689,6 +775,7 @@ async function createClone(local, opts) {
|
|
|
689
775
|
console.log(
|
|
690
776
|
`Linked this machine (${linkPath(local.configRoot)}). Push prompt changes anytime by running \`sky clone\` again.`
|
|
691
777
|
);
|
|
778
|
+
await confirmAndVaultSecrets(local.secretCandidates, opts.yes ?? false);
|
|
692
779
|
if (opts.open) openBrowser(url);
|
|
693
780
|
}
|
|
694
781
|
async function syncClone(local, link, opts) {
|
|
@@ -747,11 +834,12 @@ async function syncClone(local, link, opts) {
|
|
|
747
834
|
}
|
|
748
835
|
if (local.droppedServers.length) {
|
|
749
836
|
console.log(
|
|
750
|
-
` left out
|
|
837
|
+
` left out of clone (secret-bearing): ${local.droppedServers.map((d) => `${d.name} MCP`).join(", ")}`
|
|
751
838
|
);
|
|
752
839
|
}
|
|
753
840
|
writeLink(local.configRoot, { ...link, lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
754
841
|
console.log(`Synced ${link.agentId}.`);
|
|
842
|
+
await confirmAndVaultSecrets(local.secretCandidates, opts.yes ?? false);
|
|
755
843
|
if (opts.open) openBrowser(`${apiBase()}/agents/${link.agentId}`);
|
|
756
844
|
}
|
|
757
845
|
async function clone(opts) {
|
|
@@ -851,7 +939,7 @@ function listClaudeCodeSessions(limit) {
|
|
|
851
939
|
} catch {
|
|
852
940
|
continue;
|
|
853
941
|
}
|
|
854
|
-
const
|
|
942
|
+
const workspace2 = decodeWorkspace(encoded);
|
|
855
943
|
for (const entry2 of entries) {
|
|
856
944
|
if (!entry2.endsWith(".jsonl")) continue;
|
|
857
945
|
const sessionId = entry2.slice(0, -6);
|
|
@@ -879,7 +967,7 @@ function listClaudeCodeSessions(limit) {
|
|
|
879
967
|
sessions.push({
|
|
880
968
|
harness: "claude_code",
|
|
881
969
|
sessionId,
|
|
882
|
-
workspace,
|
|
970
|
+
workspace: workspace2,
|
|
883
971
|
title: sniffTitle(mainFile),
|
|
884
972
|
mtimeMs: stat.mtimeMs,
|
|
885
973
|
files: [mainFile, ...subagentFiles],
|
|
@@ -957,6 +1045,13 @@ function agentUseBase() {
|
|
|
957
1045
|
if (derived.includes("localhost:3000")) return derived.replace("localhost:3000", "localhost:3002");
|
|
958
1046
|
return derived;
|
|
959
1047
|
}
|
|
1048
|
+
function currentSessionEnv() {
|
|
1049
|
+
const claudeId = process.env.CLAUDE_CODE_SESSION_ID;
|
|
1050
|
+
if (claudeId) return { harness: "claude_code", sessionId: claudeId };
|
|
1051
|
+
const codexId = process.env.CODEX_THREAD_ID;
|
|
1052
|
+
if (codexId) return { harness: "codex", sessionId: codexId };
|
|
1053
|
+
return void 0;
|
|
1054
|
+
}
|
|
960
1055
|
async function mintTrace(base, harness) {
|
|
961
1056
|
const token = apiToken();
|
|
962
1057
|
if (!token) throw new ApiError(401, "not_logged_in", "not logged in \u2014 run `sky auth login`");
|
|
@@ -979,7 +1074,27 @@ async function mintTrace(base, harness) {
|
|
|
979
1074
|
}
|
|
980
1075
|
return await res.json();
|
|
981
1076
|
}
|
|
982
|
-
function
|
|
1077
|
+
function loadSummaryFile(summaryPath) {
|
|
1078
|
+
let raw;
|
|
1079
|
+
try {
|
|
1080
|
+
raw = readFileSync5(summaryPath, "utf8");
|
|
1081
|
+
} catch {
|
|
1082
|
+
throw new Error(`Cannot read summary file: ${summaryPath}`);
|
|
1083
|
+
}
|
|
1084
|
+
let pkg;
|
|
1085
|
+
try {
|
|
1086
|
+
pkg = JSON.parse(raw);
|
|
1087
|
+
} catch {
|
|
1088
|
+
throw new Error("Summary file is not valid JSON.");
|
|
1089
|
+
}
|
|
1090
|
+
if (!pkg || typeof pkg !== "object" || Array.isArray(pkg)) throw new Error("Summary file must be a JSON object.");
|
|
1091
|
+
const p = pkg;
|
|
1092
|
+
if (p.format !== "asteroids.session-summary") throw new Error('Summary file must have format "asteroids.session-summary".');
|
|
1093
|
+
if (p.format_version !== 1) throw new Error("Summary file must have format_version 1.");
|
|
1094
|
+
const redacted = redactLine2(raw.trimEnd());
|
|
1095
|
+
return redacted;
|
|
1096
|
+
}
|
|
1097
|
+
function prepareImport(session2, summaryPath) {
|
|
983
1098
|
if (session2.discoveryError) throw new Error(session2.discoveryError);
|
|
984
1099
|
const maximum = 16 * 1024 * 1024;
|
|
985
1100
|
let inputBytes = 0;
|
|
@@ -987,19 +1102,40 @@ function prepareImport(session2) {
|
|
|
987
1102
|
const lines = [];
|
|
988
1103
|
const digest = createHash("sha256");
|
|
989
1104
|
let nativeId;
|
|
1105
|
+
let redactionCount = 0;
|
|
1106
|
+
let encryptedReasoningCount = 0;
|
|
1107
|
+
let compactionCount = 0;
|
|
1108
|
+
let unreadableCount = 0;
|
|
990
1109
|
for (const path of session2.files) {
|
|
991
1110
|
const stat = statSync(path);
|
|
992
1111
|
if (!stat.isFile() || (inputBytes += stat.size) > maximum) throw new Error("Selected transcript exceeds the 16 MiB limit or is not a regular file.");
|
|
993
1112
|
const text = readFileSync5(path, "utf8");
|
|
994
1113
|
for (const raw of text.split("\n")) {
|
|
995
1114
|
if (!raw.trim()) continue;
|
|
996
|
-
|
|
1115
|
+
let record;
|
|
1116
|
+
try {
|
|
1117
|
+
record = JSON.parse(raw);
|
|
1118
|
+
} catch {
|
|
1119
|
+
unreadableCount++;
|
|
1120
|
+
continue;
|
|
1121
|
+
}
|
|
997
1122
|
if (!record || typeof record !== "object" || Array.isArray(record)) throw new Error("Transcript records must be JSON objects.");
|
|
998
|
-
|
|
999
|
-
|
|
1123
|
+
const rec = record;
|
|
1124
|
+
if (String(rec.type).startsWith("import.")) throw new Error("Native transcripts cannot supply import protocol records.");
|
|
1125
|
+
if (rec.type === "response_item") {
|
|
1126
|
+
const payload = rec.payload;
|
|
1127
|
+
if (payload?.type === "reasoning" && typeof payload?.encrypted_content === "string" && payload.encrypted_content) {
|
|
1128
|
+
encryptedReasoningCount++;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
if (rec.type === "compacted") {
|
|
1132
|
+
compactionCount++;
|
|
1133
|
+
}
|
|
1134
|
+
const id = rec.type === "session_meta" ? rec.payload?.id : rec.sessionId;
|
|
1000
1135
|
if (!nativeId && typeof id === "string") nativeId = id;
|
|
1001
|
-
const
|
|
1002
|
-
|
|
1136
|
+
const result = redactLine(raw.replace(/\r$/, ""), { requireStructured: true });
|
|
1137
|
+
if (Object.keys(result.hits).length > 0) redactionCount++;
|
|
1138
|
+
const line = result.line + "\n";
|
|
1003
1139
|
outputBytes += Buffer.byteLength(line);
|
|
1004
1140
|
if (outputBytes > maximum) throw new Error("Redacted transcript exceeds the 16 MiB limit.");
|
|
1005
1141
|
digest.update(line);
|
|
@@ -1009,27 +1145,38 @@ function prepareImport(session2) {
|
|
|
1009
1145
|
if (!lines.length) throw new Error("The selected transcript is empty.");
|
|
1010
1146
|
const sourceId = nativeId ?? session2.sessionId;
|
|
1011
1147
|
if (!/^[A-Za-z0-9_-]{1,200}$/.test(sourceId) || redactLine2(JSON.stringify(sourceId)) !== JSON.stringify(sourceId)) throw new Error("The source conversation has an invalid identifier.");
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1148
|
+
let summaryJson;
|
|
1149
|
+
if (summaryPath) {
|
|
1150
|
+
summaryJson = loadSummaryFile(summaryPath);
|
|
1151
|
+
} else {
|
|
1152
|
+
const summary = {
|
|
1153
|
+
format: "asteroids.session-summary",
|
|
1154
|
+
format_version: 1,
|
|
1155
|
+
source_harness: session2.harness,
|
|
1156
|
+
source_harness_version: null,
|
|
1157
|
+
source_session_id: sourceId,
|
|
1158
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1159
|
+
summary: "The complete selected local conversation is attached as a transcript. Review it before continuing; this automatic handoff does not infer the user's unfinished objective or claim any work is complete.",
|
|
1160
|
+
context: {
|
|
1161
|
+
objective: "Review the imported conversation and continue its latest unfinished authorized objective.",
|
|
1162
|
+
constraints: [],
|
|
1163
|
+
decisions: [],
|
|
1164
|
+
completed: [],
|
|
1165
|
+
pending: ["Read the complete imported transcript, reconcile the latest human instructions and completed work, then continue."],
|
|
1166
|
+
verification: [],
|
|
1167
|
+
blockers: ["This handoff contains the recorded conversation only. Referenced local files, runtime state, and unreadable reasoning are not transferred."],
|
|
1168
|
+
artifacts: [],
|
|
1169
|
+
provenance: { native_checkpoint_sha256: digest.digest("hex"), selected_native_session_id: sourceId, context_bytes: outputBytes }
|
|
1170
|
+
}
|
|
1171
|
+
};
|
|
1172
|
+
summaryJson = JSON.stringify(summary);
|
|
1173
|
+
}
|
|
1174
|
+
const body = lines.join("") + JSON.stringify({ type: "import.summary", package: JSON.parse(summaryJson) }) + "\n" + JSON.stringify({ type: "import.end", records: lines.length + 1 }) + "\n";
|
|
1175
|
+
const gaps = [];
|
|
1176
|
+
if (encryptedReasoningCount > 0) gaps.push({ kind: "encrypted_reasoning", count: encryptedReasoningCount });
|
|
1177
|
+
if (compactionCount > 0) gaps.push({ kind: "compaction", count: compactionCount });
|
|
1178
|
+
if (unreadableCount > 0) gaps.push({ kind: "unreadable", count: unreadableCount });
|
|
1179
|
+
return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps };
|
|
1033
1180
|
}
|
|
1034
1181
|
async function importSession(opts) {
|
|
1035
1182
|
const base = (opts.agentUseUrl ?? agentUseBase()).replace(/\/$/, "");
|
|
@@ -1038,9 +1185,16 @@ async function importSession(opts) {
|
|
|
1038
1185
|
throw new Error("Import requires HTTPS, or a loopback development server.");
|
|
1039
1186
|
}
|
|
1040
1187
|
opts.onProgress?.("Reading transcript and redacting secrets locally\u2026");
|
|
1041
|
-
const body = prepareImport(opts.session);
|
|
1042
|
-
|
|
1043
|
-
|
|
1188
|
+
const { body, transcriptLines, outputBytes, redactionCount, gaps } = prepareImport(opts.session, opts.summaryPath);
|
|
1189
|
+
let traceId;
|
|
1190
|
+
let key;
|
|
1191
|
+
if (opts.importKey && opts.grantTraceId) {
|
|
1192
|
+
traceId = opts.grantTraceId;
|
|
1193
|
+
key = opts.importKey;
|
|
1194
|
+
} else {
|
|
1195
|
+
opts.onProgress?.("Minting import key\u2026");
|
|
1196
|
+
({ traceId, key } = await mintTrace(base, opts.session.harness));
|
|
1197
|
+
}
|
|
1044
1198
|
opts.onProgress?.("Uploading the complete transcript\u2026");
|
|
1045
1199
|
const response = await fetch(`${base}/api/traces/ingest`, {
|
|
1046
1200
|
method: "POST",
|
|
@@ -1054,7 +1208,17 @@ async function importSession(opts) {
|
|
|
1054
1208
|
throw new Error(typeof result2?.error === "string" ? result2.error : result2?.error?.message ?? `Upload failed (${response.status}).`);
|
|
1055
1209
|
}
|
|
1056
1210
|
const result = await response.json();
|
|
1057
|
-
|
|
1211
|
+
const finalTraceId = result.traceId ?? traceId;
|
|
1212
|
+
return {
|
|
1213
|
+
traceId: finalTraceId,
|
|
1214
|
+
url: `${base}/playground/new?import=${encodeURIComponent(finalTraceId)}`,
|
|
1215
|
+
harness: opts.session.harness,
|
|
1216
|
+
sessionId: opts.session.sessionId,
|
|
1217
|
+
transcriptFiles: opts.session.files.length,
|
|
1218
|
+
bytes: outputBytes,
|
|
1219
|
+
redactionCount,
|
|
1220
|
+
gaps
|
|
1221
|
+
};
|
|
1058
1222
|
}
|
|
1059
1223
|
|
|
1060
1224
|
// src/login.ts
|
|
@@ -1332,12 +1496,13 @@ function renderCatalog(agents, generatedAt) {
|
|
|
1332
1496
|
}
|
|
1333
1497
|
|
|
1334
1498
|
// src/skill.ts
|
|
1499
|
+
var REFERENCES_DIR = "references";
|
|
1335
1500
|
var SKILL_NAME = "agentsky";
|
|
1336
1501
|
var SKILL_MARKER = ".agentsky.json";
|
|
1337
1502
|
function skillDocument() {
|
|
1338
1503
|
return `---
|
|
1339
1504
|
name: ${SKILL_NAME}
|
|
1340
|
-
description: "Hire cloud agents from the AgentSky catalog (included as ${CATALOG_FILE}) and coordinate them on one task. Use when the person names an agent by @handle
|
|
1505
|
+
description: "Hire cloud agents from the AgentSky catalog (included as ${CATALOG_FILE}) and coordinate them on one task. Use when: the person names an agent by @handle or wants to orchestrate agents; asks another engine (Codex, Claude Code, Gemini, Hermes) to do, test or review part of the work; wants to import, move or continue this conversation in AgentSky or the cloud; names a workspace; or the work splits into independent parts that can run at the same time. Works through the agentsky MCP server, or the sky command where those tools are missing."
|
|
1341
1506
|
---
|
|
1342
1507
|
|
|
1343
1508
|
# Working with other agents through AgentSky
|
|
@@ -1352,6 +1517,14 @@ Everything below is a tool of the \`agentsky\` MCP server. If those tools are
|
|
|
1352
1517
|
missing in this session, use the \`sky\` commands under "Without the MCP tools"
|
|
1353
1518
|
at the end; they do the same things, one agent at a time.
|
|
1354
1519
|
|
|
1520
|
+
## Workspace
|
|
1521
|
+
|
|
1522
|
+
Use the workspace the person named. If they named none, check the git root for
|
|
1523
|
+
\`.agentsky.json\` \u2014 if it has a \`"workspace"\` key, use that. Otherwise use the
|
|
1524
|
+
account default (personal). Never ask. Pass the chosen slug as \`workspace\` on
|
|
1525
|
+
every MCP tool that accepts it, or as \`--workspace <slug>\` on sky commands.
|
|
1526
|
+
Details: \`${REFERENCES_DIR}/workspaces.md\` in this skill's folder.
|
|
1527
|
+
|
|
1355
1528
|
## When to hire
|
|
1356
1529
|
|
|
1357
1530
|
- When the person names an agent (\`@handle\`) or an engine, or asks another agent
|
|
@@ -1362,9 +1535,6 @@ at the end; they do the same things, one agent at a time.
|
|
|
1362
1535
|
going after this conversation ends.
|
|
1363
1536
|
- Do the work yourself when it is small, when it depends on what you already have
|
|
1364
1537
|
in front of you, or when explaining it would take as long as doing it.
|
|
1365
|
-
- Every hire spends the person's AgentSky balance. Say what you are about to hire
|
|
1366
|
-
and roughly what it may cost before the first one, unless they already asked
|
|
1367
|
-
for it.
|
|
1368
1538
|
|
|
1369
1539
|
## How to pick
|
|
1370
1540
|
|
|
@@ -1432,6 +1602,24 @@ it, and name its path in the brief.
|
|
|
1432
1602
|
6. Tell the person what was done, which agent did which part, and what it cost
|
|
1433
1603
|
(\`get_task\` reports the task's cost).
|
|
1434
1604
|
|
|
1605
|
+
## Import this conversation to AgentSky
|
|
1606
|
+
|
|
1607
|
+
When the person asks to import, move, or continue this conversation in AgentSky
|
|
1608
|
+
or the cloud:
|
|
1609
|
+
|
|
1610
|
+
1. Write a summary JSON file following the schema in \`${REFERENCES_DIR}/import.md\`
|
|
1611
|
+
(the only step that needs judgment \u2014 capture the objective, key decisions,
|
|
1612
|
+
completed work, and next steps).
|
|
1613
|
+
2. Run:
|
|
1614
|
+
\`\`\`
|
|
1615
|
+
npx -y @agentsky/sky import --current --summary <path-to-summary.json> --yes --json
|
|
1616
|
+
\`\`\`
|
|
1617
|
+
Add \`--workspace <slug>\` when a workspace was chosen. Do NOT confirm or ask
|
|
1618
|
+
before running; the \`--yes --json\` flags suppress prompts.
|
|
1619
|
+
3. Report the \`continueUrl\` from the JSON result. That is all.
|
|
1620
|
+
|
|
1621
|
+
Only stop (non-zero exit) on a size limit, dropped content, or redaction failure.
|
|
1622
|
+
|
|
1435
1623
|
## Without the MCP tools
|
|
1436
1624
|
|
|
1437
1625
|
Run \`sky auth whoami\`. If it says you are not logged in, ask the person to run
|
|
@@ -1451,6 +1639,88 @@ Run \`sky auth whoami\`. If it says you are not logged in, ask the person to run
|
|
|
1451
1639
|
read again.
|
|
1452
1640
|
`;
|
|
1453
1641
|
}
|
|
1642
|
+
function importReferenceDoc() {
|
|
1643
|
+
return `# Import this conversation to AgentSky
|
|
1644
|
+
|
|
1645
|
+
## Summary schema (asteroids.session-summary v1)
|
|
1646
|
+
|
|
1647
|
+
Write a JSON file with these fields:
|
|
1648
|
+
|
|
1649
|
+
\`\`\`json
|
|
1650
|
+
{
|
|
1651
|
+
"format": "asteroids.session-summary",
|
|
1652
|
+
"format_version": 1,
|
|
1653
|
+
"source_harness": "claude_code",
|
|
1654
|
+
"source_harness_version": null,
|
|
1655
|
+
"source_session_id": "<the session id, e.g. from CLAUDE_CODE_SESSION_ID>",
|
|
1656
|
+
"created_at": "<ISO 8601 timestamp>",
|
|
1657
|
+
"summary": "<one paragraph: what this conversation accomplished>",
|
|
1658
|
+
"context": {
|
|
1659
|
+
"objective": "<what the person ultimately wants>",
|
|
1660
|
+
"constraints": ["<must not do X>"],
|
|
1661
|
+
"decisions": ["<decided to use Y approach>"],
|
|
1662
|
+
"completed": ["<finished task A>"],
|
|
1663
|
+
"pending": ["<next: do B>"],
|
|
1664
|
+
"verification": ["<how to confirm B is correct>"],
|
|
1665
|
+
"blockers": ["<needs access to X>"],
|
|
1666
|
+
"artifacts": [{"path": "path/to/file.ts", "description": "what it is"}]
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
\`\`\`
|
|
1670
|
+
|
|
1671
|
+
Only \`summary\` and \`context\` require judgment. All other fields are fixed values
|
|
1672
|
+
or can be omitted (the CLI validates \`format\` and \`format_version\`).
|
|
1673
|
+
|
|
1674
|
+
## Gaps
|
|
1675
|
+
|
|
1676
|
+
The import transfers the recorded conversation only. These are NOT transferred:
|
|
1677
|
+
- Local files not included in the transcript
|
|
1678
|
+
- Runtime state (variables, shell state, running processes)
|
|
1679
|
+
- Unrecorded tool outputs or reasoning
|
|
1680
|
+
- Login credentials and secrets
|
|
1681
|
+
|
|
1682
|
+
List known gaps in \`context.blockers\`.
|
|
1683
|
+
|
|
1684
|
+
## Non-Claude/Codex harnesses
|
|
1685
|
+
|
|
1686
|
+
For harnesses that do not set \`CLAUDE_CODE_SESSION_ID\` or \`CODEX_THREAD_ID\`:
|
|
1687
|
+
use \`--session <id>\` instead of \`--current\`, and set \`source_harness\` to the
|
|
1688
|
+
harness name (e.g. \`"hermes"\`, \`"openclaw"\`).
|
|
1689
|
+
`;
|
|
1690
|
+
}
|
|
1691
|
+
function workspacesReferenceDoc() {
|
|
1692
|
+
return `# Workspaces in AgentSky
|
|
1693
|
+
|
|
1694
|
+
A workspace (also called a universe) groups agents and sessions. Every API call
|
|
1695
|
+
and MCP tool accepts an optional workspace slug.
|
|
1696
|
+
|
|
1697
|
+
## How to choose
|
|
1698
|
+
|
|
1699
|
+
1. The person named one \u2192 use it.
|
|
1700
|
+
2. Check the git root for \`.agentsky.json\` with a \`"workspace"\` key \u2192 use that.
|
|
1701
|
+
3. Neither \u2192 use the account default (personal workspace). Never ask.
|
|
1702
|
+
|
|
1703
|
+
## Applying it
|
|
1704
|
+
|
|
1705
|
+
- MCP tools: pass \`workspace: "<slug>"\` on tools that accept it.
|
|
1706
|
+
- sky CLI: \`--workspace <slug>\` on any command, or set \`SKY_WORKSPACE=<slug>\`.
|
|
1707
|
+
- Tools that take a \`task_id\` or \`session_id\` inherit that task's workspace
|
|
1708
|
+
automatically; one task never spans workspaces.
|
|
1709
|
+
|
|
1710
|
+
## Listing workspaces
|
|
1711
|
+
|
|
1712
|
+
\`sky workspace list\` (or \`sky universe list\`) prints your workspaces, with
|
|
1713
|
+
\`*\` marking the current default.
|
|
1714
|
+
|
|
1715
|
+
## .agentsky.json
|
|
1716
|
+
|
|
1717
|
+
To set a per-repo default, create \`.agentsky.json\` at the git root:
|
|
1718
|
+
|
|
1719
|
+
\`\`\`json
|
|
1720
|
+
{"workspace": "my-team-slug"}
|
|
1721
|
+
\`\`\`
|
|
1722
|
+
`;
|
|
1723
|
+
}
|
|
1454
1724
|
function installedVersion(dir) {
|
|
1455
1725
|
try {
|
|
1456
1726
|
const marker = JSON.parse(readFileSync6(join5(dir, SKILL_MARKER), "utf8"));
|
|
@@ -1477,6 +1747,10 @@ function installSkill(agent2, opts = {}) {
|
|
|
1477
1747
|
mkdirSync2(dir, { recursive: true });
|
|
1478
1748
|
writeFileSync4(join5(dir, CATALOG_FILE), renderCatalog(opts.catalog, opts.now ?? /* @__PURE__ */ new Date()));
|
|
1479
1749
|
}
|
|
1750
|
+
const refsDir = join5(dir, REFERENCES_DIR);
|
|
1751
|
+
mkdirSync2(refsDir, { recursive: true });
|
|
1752
|
+
writeFileSync4(join5(refsDir, "import.md"), importReferenceDoc());
|
|
1753
|
+
writeFileSync4(join5(refsDir, "workspaces.md"), workspacesReferenceDoc());
|
|
1480
1754
|
return { agent: agent2.id, path: dir, outcome };
|
|
1481
1755
|
}
|
|
1482
1756
|
|
|
@@ -2328,7 +2602,11 @@ function printModelBilling(session2) {
|
|
|
2328
2602
|
console.log(`Model billing: ${label} \u2014 $0 model usage`);
|
|
2329
2603
|
}
|
|
2330
2604
|
var program = new Command();
|
|
2331
|
-
program.name("sky").description("Launch and talk to long-lived agents \u2014 agentsky.dev").version(CLI_VERSION);
|
|
2605
|
+
program.name("sky").description("Launch and talk to long-lived agents \u2014 agentsky.dev").version(CLI_VERSION).option("--workspace <slug>", "workspace to act in (overrides sky universe switch and SKY_UNIVERSE; alias of --universe on sub-commands)");
|
|
2606
|
+
program.hook("preAction", () => {
|
|
2607
|
+
const workspace2 = program.opts().workspace;
|
|
2608
|
+
if (workspace2) process.env.SKY_WORKSPACE = workspace2;
|
|
2609
|
+
});
|
|
2332
2610
|
function fail(error) {
|
|
2333
2611
|
if (error instanceof ApiError) console.error(`sky: ${error.message} (${error.code})`);
|
|
2334
2612
|
else console.error(`sky: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -2430,8 +2708,19 @@ universe.command("remove <member>").description("remove a member (user id or ema
|
|
|
2430
2708
|
);
|
|
2431
2709
|
universe.command("join <token>").description("accept an invitation sent to your verified email").action(
|
|
2432
2710
|
run(async (token) => {
|
|
2433
|
-
const
|
|
2434
|
-
console.log(`Joined ${
|
|
2711
|
+
const workspace2 = await joinWorkspace(token);
|
|
2712
|
+
console.log(`Joined ${workspace2.slug} (${workspace2.name}) \u2014 sky universe switch ${workspace2.slug}`);
|
|
2713
|
+
})
|
|
2714
|
+
);
|
|
2715
|
+
var workspace = program.command("workspace").description("alias of universe: grouping boundary for agents");
|
|
2716
|
+
workspace.command("list").action(
|
|
2717
|
+
run(async () => {
|
|
2718
|
+
const { universes } = await api("GET", "/universes");
|
|
2719
|
+
const current = currentUniverse();
|
|
2720
|
+
for (const u of universes) {
|
|
2721
|
+
const marker = u.slug === current || !current && u.isPersonal ? "*" : " ";
|
|
2722
|
+
console.log(`${marker} ${u.slug}${u.isPersonal ? " (personal)" : ""}`);
|
|
2723
|
+
}
|
|
2435
2724
|
})
|
|
2436
2725
|
);
|
|
2437
2726
|
var policy = program.command("policy").description("policy rules: tool calls, spend caps, call counts, capabilities");
|
|
@@ -2993,30 +3282,54 @@ function formatRelative(msAgo) {
|
|
|
2993
3282
|
if (hours < 48) return `${hours}h ago`;
|
|
2994
3283
|
return `${Math.round(hours / 24)}d ago`;
|
|
2995
3284
|
}
|
|
2996
|
-
program.command("import").description("import a local Claude Code or Codex session to AgentSky and continue it in the cloud").option("--all", "import all found sessions up to --limit").option("-y, --yes", "confirm transfer
|
|
3285
|
+
program.command("import").description("import a local Claude Code or Codex session to AgentSky and continue it in the cloud").option("--current", "import the session this agent is running in (reads CLAUDE_CODE_SESSION_ID or CODEX_THREAD_ID, then falls back to most recent session in the cwd)").option("--all", "import all found sessions up to --limit").option("-y, --yes", "confirm transfer without prompting (default when --current or --session is given in non-TTY)").option("--json", "print one JSON result object per import and nothing else to stdout").option("--limit <n>", "maximum sessions to list or import", "10").option("--session <id>", "import one specific session by its ID").option("--harness <type>", "claude_code | codex | all (default: all)").option("--summary <path>", "attach a pre-written summary JSON (asteroids.session-summary v1) instead of the auto-generated one").option("--key <importKey>", "import grant key minted by the web page (skips sky auth login)").option("--trace <traceId>", "trace id paired with --key").option("--url <base>", "agent-use app URL (default: derived from SKY_API_URL or AGENT_USE_URL)").action(
|
|
2997
3286
|
run(
|
|
2998
3287
|
async (opts) => {
|
|
3288
|
+
if (opts.key && !opts.trace) throw new Error("--key requires --trace <traceId>");
|
|
3289
|
+
if (opts.trace && !opts.key) throw new Error("--trace requires --key <importKey>");
|
|
2999
3290
|
const limit = Math.max(1, Math.min(50, parseInt(opts.limit ?? "10", 10) || 10));
|
|
3000
3291
|
if (opts.harness && !["claude_code", "codex", "all"].includes(opts.harness)) throw new Error("Use --harness claude_code, codex, or all.");
|
|
3001
3292
|
const harness = opts.harness ?? "all";
|
|
3002
3293
|
const base = opts.url ?? agentUseBase();
|
|
3003
|
-
const
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
if (
|
|
3007
|
-
|
|
3008
|
-
|
|
3294
|
+
const nonInteractive = !process.stdin.isTTY;
|
|
3295
|
+
const autoConfirm = opts.yes || opts.current || !!opts.session && nonInteractive;
|
|
3296
|
+
let candidates;
|
|
3297
|
+
if (opts.current) {
|
|
3298
|
+
const env = currentSessionEnv();
|
|
3299
|
+
if (env) {
|
|
3300
|
+
candidates = listRecentSessions(env.harness, Number.POSITIVE_INFINITY).filter(
|
|
3301
|
+
(s) => s.sessionId === env.sessionId
|
|
3302
|
+
);
|
|
3303
|
+
if (candidates.length === 0) {
|
|
3304
|
+
const cwd = process.cwd();
|
|
3305
|
+
const all = listRecentSessions(env.harness, 50);
|
|
3306
|
+
const byCwd = all.filter((s) => s.workspace === cwd || s.workspace.startsWith(cwd + "/"));
|
|
3307
|
+
candidates = byCwd.length > 0 ? [byCwd[0]] : all.length > 0 ? [all[0]] : [];
|
|
3308
|
+
}
|
|
3309
|
+
} else {
|
|
3310
|
+
const cwd = process.cwd();
|
|
3311
|
+
const all = listRecentSessions(harness === "all" ? void 0 : harness, 50);
|
|
3312
|
+
const byCwd = all.filter((s) => s.workspace === cwd || s.workspace.startsWith(cwd + "/"));
|
|
3313
|
+
candidates = byCwd.length > 0 ? [byCwd[0]] : all.length > 0 ? [all[0]] : [];
|
|
3009
3314
|
}
|
|
3010
|
-
|
|
3011
|
-
|
|
3315
|
+
if (candidates.length === 0) throw new Error("No local session found for the current environment. Run with --session <id> or start an agent session first.");
|
|
3316
|
+
} else if (opts.session) {
|
|
3317
|
+
candidates = listRecentSessions(harness === "all" ? void 0 : harness, Number.POSITIVE_INFINITY).filter(
|
|
3318
|
+
(s) => s.sessionId === opts.session
|
|
3012
3319
|
);
|
|
3013
|
-
|
|
3320
|
+
if (candidates.length === 0) throw new Error(`Session "${opts.session}" not found in ~/.claude or ~/.codex.`);
|
|
3321
|
+
} else {
|
|
3322
|
+
candidates = listRecentSessions(harness === "all" ? void 0 : harness, limit);
|
|
3323
|
+
if (candidates.length === 0) {
|
|
3324
|
+
console.log("No recent sessions found. Make sure you have run Claude Code (sessions in ~/.claude) or Codex (sessions in ~/.codex).");
|
|
3325
|
+
return;
|
|
3326
|
+
}
|
|
3014
3327
|
}
|
|
3015
3328
|
let toImport;
|
|
3016
|
-
if (opts.all || opts.session || candidates.length === 1) {
|
|
3329
|
+
if (opts.current || opts.all || opts.session || candidates.length === 1) {
|
|
3017
3330
|
toImport = candidates;
|
|
3018
3331
|
} else {
|
|
3019
|
-
if (
|
|
3332
|
+
if (nonInteractive) throw new Error("Use --session, --current, or --all to select conversations in a non-interactive terminal.");
|
|
3020
3333
|
console.log("\nRecent local sessions:\n");
|
|
3021
3334
|
candidates.forEach((s, i) => {
|
|
3022
3335
|
const label = s.title ? `"${s.title.slice(0, 70)}"` : "(no title)";
|
|
@@ -3049,10 +3362,12 @@ program.command("import").description("import a local Claude Code or Codex sessi
|
|
|
3049
3362
|
}
|
|
3050
3363
|
}
|
|
3051
3364
|
toImport = [...new Map(toImport.map((session2) => [`${session2.harness}:${session2.sessionId}`, session2])).values()];
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3365
|
+
if (!opts.json) {
|
|
3366
|
+
console.log(`Selected ${toImport.length} conversation(s), ${toImport.reduce((count, session2) => count + session2.files.length, 0)} transcript file(s). Destination: ${base}`);
|
|
3367
|
+
console.log("The recorded conversation is redacted locally. Local project files and login credentials are not transferred.");
|
|
3368
|
+
}
|
|
3369
|
+
if (!autoConfirm) {
|
|
3370
|
+
if (nonInteractive) throw new Error("Use --yes to confirm this transfer in a non-interactive terminal.");
|
|
3056
3371
|
const prompt = createInterface4({ input: process.stdin, output: process.stdout });
|
|
3057
3372
|
const answer = await new Promise((resolve5) => prompt.question("Upload these selected conversations? [y/N] ", (value) => {
|
|
3058
3373
|
prompt.close();
|
|
@@ -3065,18 +3380,36 @@ program.command("import").description("import a local Claude Code or Codex sessi
|
|
|
3065
3380
|
}
|
|
3066
3381
|
for (const session2 of toImport) {
|
|
3067
3382
|
const label = session2.title ? `"${session2.title.slice(0, 60)}"` : session2.sessionId;
|
|
3068
|
-
process.stdout.write(`
|
|
3383
|
+
if (!opts.json) process.stdout.write(`
|
|
3069
3384
|
Importing ${label} \u2026
|
|
3070
3385
|
`);
|
|
3071
3386
|
const result = await importSession({
|
|
3072
3387
|
session: session2,
|
|
3073
3388
|
agentUseUrl: base,
|
|
3074
|
-
|
|
3389
|
+
summaryPath: opts.summary,
|
|
3390
|
+
importKey: opts.key,
|
|
3391
|
+
grantTraceId: opts.trace,
|
|
3392
|
+
onProgress: opts.json ? void 0 : (msg) => process.stdout.write(` ${msg}
|
|
3075
3393
|
`)
|
|
3076
3394
|
});
|
|
3077
|
-
|
|
3395
|
+
if (opts.json) {
|
|
3396
|
+
console.log(JSON.stringify({
|
|
3397
|
+
traceId: result.traceId,
|
|
3398
|
+
url: result.url,
|
|
3399
|
+
workspace: currentUniverse() ?? null,
|
|
3400
|
+
harness: result.harness,
|
|
3401
|
+
sessionId: result.sessionId,
|
|
3402
|
+
transcriptFiles: result.transcriptFiles,
|
|
3403
|
+
bytes: result.bytes,
|
|
3404
|
+
redactionCount: result.redactionCount,
|
|
3405
|
+
gaps: result.gaps,
|
|
3406
|
+
continueUrl: result.url
|
|
3407
|
+
}));
|
|
3408
|
+
} else {
|
|
3409
|
+
console.log(` \u2713 Imported. Continue here:
|
|
3078
3410
|
${result.url}
|
|
3079
3411
|
`);
|
|
3412
|
+
}
|
|
3080
3413
|
}
|
|
3081
3414
|
}
|
|
3082
3415
|
)
|