youmd 0.8.29 → 0.9.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/commands/orchestrate.d.ts +12 -0
- package/dist/commands/orchestrate.d.ts.map +1 -0
- package/dist/commands/orchestrate.js +273 -0
- package/dist/commands/orchestrate.js.map +1 -0
- package/dist/commands/remote.d.ts +8 -0
- package/dist/commands/remote.d.ts.map +1 -1
- package/dist/commands/remote.js +8 -0
- package/dist/commands/remote.js.map +1 -1
- package/dist/commands/stack.d.ts.map +1 -1
- package/dist/commands/stack.js +37 -3
- package/dist/commands/stack.js.map +1 -1
- package/dist/commands/status.js +1 -1
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/storage.d.ts +7 -0
- package/dist/commands/storage.d.ts.map +1 -0
- package/dist/commands/storage.js +220 -0
- package/dist/commands/storage.js.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/api.d.ts +32 -0
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +21 -0
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/config.d.ts +15 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/daemon.d.ts +19 -0
- package/dist/lib/daemon.d.ts.map +1 -1
- package/dist/lib/daemon.js +72 -1
- package/dist/lib/daemon.js.map +1 -1
- package/dist/lib/foldermd.d.ts +70 -0
- package/dist/lib/foldermd.d.ts.map +1 -0
- package/dist/lib/foldermd.js +226 -0
- package/dist/lib/foldermd.js.map +1 -0
- package/dist/lib/orchestrator/loop.d.ts +55 -0
- package/dist/lib/orchestrator/loop.d.ts.map +1 -0
- package/dist/lib/orchestrator/loop.js +248 -0
- package/dist/lib/orchestrator/loop.js.map +1 -0
- package/dist/lib/orchestrator/supervisor.d.ts +80 -0
- package/dist/lib/orchestrator/supervisor.d.ts.map +1 -0
- package/dist/lib/orchestrator/supervisor.js +315 -0
- package/dist/lib/orchestrator/supervisor.js.map +1 -0
- package/dist/lib/orchestrator/tools.d.ts +16 -0
- package/dist/lib/orchestrator/tools.d.ts.map +1 -0
- package/dist/lib/orchestrator/tools.js +258 -0
- package/dist/lib/orchestrator/tools.js.map +1 -0
- package/dist/lib/remote-executor.d.ts +15 -1
- package/dist/lib/remote-executor.d.ts.map +1 -1
- package/dist/lib/remote-executor.js +195 -0
- package/dist/lib/remote-executor.js.map +1 -1
- package/dist/mcp/registry.d.ts.map +1 -1
- package/dist/mcp/registry.js +89 -0
- package/dist/mcp/registry.js.map +1 -1
- package/package.json +1 -1
- package/scripts/skillstack-sync/com.you.orchestrator-watch.plist +38 -0
- package/scripts/skillstack-sync/install-daemons-linux.sh +168 -0
- package/scripts/skillstack-sync/install-daemons.sh +1 -1
package/dist/lib/daemon.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../../src/lib/daemon.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../../src/lib/daemon.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export declare const FOLDERMD_DEFAULT_BASE = "https://www.folder.md/api/v1";
|
|
2
|
+
export interface FolderMdAuth {
|
|
3
|
+
apiKey: string;
|
|
4
|
+
baseUrl?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface FolderMdFile {
|
|
7
|
+
id: string;
|
|
8
|
+
path?: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
size?: number;
|
|
11
|
+
mimeType?: string;
|
|
12
|
+
[k: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
/** Resolve a folder.md API key from explicit arg → config → env. Returns null if none. */
|
|
15
|
+
export declare function resolveFolderMdKey(explicit?: string): string | null;
|
|
16
|
+
/**
|
|
17
|
+
* Resolve a usable folder.md key with ZERO user paste. Explicit/env/config key
|
|
18
|
+
* wins; otherwise auto-provision via the you.md backend (mints a scoped key
|
|
19
|
+
* server-to-server) and cache it locally. Returns the key + the media folderId
|
|
20
|
+
* when provisioning supplied one. Throws a friendly error if not authenticated
|
|
21
|
+
* or if provisioning is not configured on the backend.
|
|
22
|
+
*/
|
|
23
|
+
export declare function ensureProvisionedKey(explicit?: string): Promise<{
|
|
24
|
+
apiKey: string;
|
|
25
|
+
folderId?: string;
|
|
26
|
+
}>;
|
|
27
|
+
/**
|
|
28
|
+
* Resolve the folder id to use for this user's media, creating + persisting a dedicated
|
|
29
|
+
* "you.md media" folder if none is configured. Shared by the CLI and the MCP tools so the
|
|
30
|
+
* "which folder?" logic lives in one place.
|
|
31
|
+
*/
|
|
32
|
+
export declare function ensureUserFolder(apiKey: string, explicit?: string): Promise<string>;
|
|
33
|
+
/** List the user's folders (workspaces). */
|
|
34
|
+
export declare function listFolders(auth: FolderMdAuth): Promise<unknown[]>;
|
|
35
|
+
/** Create a folder (workspace), e.g. a per-you.md-user media folder. */
|
|
36
|
+
export declare function createFolder(auth: FolderMdAuth, name: string): Promise<FolderMdFile>;
|
|
37
|
+
/** List files in a folder. */
|
|
38
|
+
export declare function listFiles(auth: FolderMdAuth, folderId: string): Promise<FolderMdFile[]>;
|
|
39
|
+
/**
|
|
40
|
+
* Upload a local file (binary or text) to a folder via multipart. This is the large-file/media
|
|
41
|
+
* path the brain cannot handle. Returns the created file record (store its `id` as the brain
|
|
42
|
+
* pointer). For text-only content the folder.md MCP `file_write` tool is an alternative.
|
|
43
|
+
*/
|
|
44
|
+
export declare function uploadFile(auth: FolderMdAuth, folderId: string, localPath: string, options?: {
|
|
45
|
+
destPath?: string;
|
|
46
|
+
mimeType?: string;
|
|
47
|
+
}): Promise<FolderMdFile>;
|
|
48
|
+
/**
|
|
49
|
+
* Download a file's bytes. folder.md storage is private (no public CDN URL yet), so bytes come
|
|
50
|
+
* back through the authenticated API; you.md proxies/streams them rather than handing out a URL.
|
|
51
|
+
*/
|
|
52
|
+
export declare function downloadFile(auth: FolderMdAuth, folderId: string, fileId: string, destPath: string): Promise<{
|
|
53
|
+
bytes: number;
|
|
54
|
+
path: string;
|
|
55
|
+
}>;
|
|
56
|
+
/**
|
|
57
|
+
* The brain pointer: what you.md stores (as a plain string, which the bundle handles perfectly)
|
|
58
|
+
* to reference a media artifact living in folder.md. Embed JSON.stringify(this) in a memory or
|
|
59
|
+
* custom_file. No bytes ever enter the brain.
|
|
60
|
+
*/
|
|
61
|
+
export interface BrainMediaPointer {
|
|
62
|
+
provider: "folder.md";
|
|
63
|
+
folderId: string;
|
|
64
|
+
fileId: string;
|
|
65
|
+
name: string;
|
|
66
|
+
mimeType?: string;
|
|
67
|
+
size?: number;
|
|
68
|
+
uploadedAt: string;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=foldermd.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"foldermd.d.ts","sourceRoot":"","sources":["../../src/lib/foldermd.ts"],"names":[],"mappings":"AAkBA,eAAO,MAAM,qBAAqB,iCAAiC,CAAC;AAEpE,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB;AAED,0FAA0F;AAC1F,wBAAgB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUnE;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAwBhD;AAoBD;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAgBzF;AAWD,4CAA4C;AAC5C,wBAAsB,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAQxE;AAED,wEAAwE;AACxE,wBAAsB,YAAY,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAS1F;AAED,8BAA8B;AAC9B,wBAAsB,SAAS,CAAC,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAQ7F;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,YAAY,EAClB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GACrD,OAAO,CAAC,YAAY,CAAC,CAiBvB;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,YAAY,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAQ1C;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,WAAW,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB"}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// foldermd.ts — You.md ↔ folder.md client (large-file / media offload).
|
|
3
|
+
//
|
|
4
|
+
// You.md is a structured-TEXT brain: every "file" is a markdown/JSON string in a ~1MB Convex
|
|
5
|
+
// bundle, mirrored to GitHub under a 700KB text-only cap. There is NO binary/large-file path.
|
|
6
|
+
// folder.md (Houston's own agent-native storage product) fills that slot. The brain stays
|
|
7
|
+
// text-first and stores only a POINTER (folder.md folder/file id); the bytes live in folder.md.
|
|
8
|
+
//
|
|
9
|
+
// Auth is a per-folder Bearer API key (`fmd_live_…`). ZERO-USER-WORK provisioning now exists:
|
|
10
|
+
// `ensureProvisionedKey()` calls the you.md backend (`POST /api/v1/me/storage/provision`),
|
|
11
|
+
// which mints a scoped folder.md key server-to-server (shared FOLDERMD_SERVICE_SECRET) and
|
|
12
|
+
// returns it to the authenticated owner's client, cached locally. The user never pastes a key.
|
|
13
|
+
// An explicit key (config / FOLDER_API_KEY / FOLDERMD_API_KEY) still wins when present.
|
|
14
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
17
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
18
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
19
|
+
}
|
|
20
|
+
Object.defineProperty(o, k2, desc);
|
|
21
|
+
}) : (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
o[k2] = m[k];
|
|
24
|
+
}));
|
|
25
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
26
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
27
|
+
}) : function(o, v) {
|
|
28
|
+
o["default"] = v;
|
|
29
|
+
});
|
|
30
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
31
|
+
var ownKeys = function(o) {
|
|
32
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
33
|
+
var ar = [];
|
|
34
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
35
|
+
return ar;
|
|
36
|
+
};
|
|
37
|
+
return ownKeys(o);
|
|
38
|
+
};
|
|
39
|
+
return function (mod) {
|
|
40
|
+
if (mod && mod.__esModule) return mod;
|
|
41
|
+
var result = {};
|
|
42
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
43
|
+
__setModuleDefault(result, mod);
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
})();
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
exports.FOLDERMD_DEFAULT_BASE = void 0;
|
|
49
|
+
exports.resolveFolderMdKey = resolveFolderMdKey;
|
|
50
|
+
exports.ensureProvisionedKey = ensureProvisionedKey;
|
|
51
|
+
exports.ensureUserFolder = ensureUserFolder;
|
|
52
|
+
exports.listFolders = listFolders;
|
|
53
|
+
exports.createFolder = createFolder;
|
|
54
|
+
exports.listFiles = listFiles;
|
|
55
|
+
exports.uploadFile = uploadFile;
|
|
56
|
+
exports.downloadFile = downloadFile;
|
|
57
|
+
const fs = __importStar(require("fs"));
|
|
58
|
+
const path = __importStar(require("path"));
|
|
59
|
+
const config_1 = require("./config");
|
|
60
|
+
const api_1 = require("./api");
|
|
61
|
+
exports.FOLDERMD_DEFAULT_BASE = "https://www.folder.md/api/v1";
|
|
62
|
+
/** Resolve a folder.md API key from explicit arg → config → env. Returns null if none. */
|
|
63
|
+
function resolveFolderMdKey(explicit) {
|
|
64
|
+
if (explicit && explicit.trim())
|
|
65
|
+
return explicit.trim();
|
|
66
|
+
const cfg = (0, config_1.readGlobalConfig)();
|
|
67
|
+
const fromCfg = typeof cfg.folderMdKey === "string" ? cfg.folderMdKey : undefined;
|
|
68
|
+
return (fromCfg ||
|
|
69
|
+
process.env.FOLDER_API_KEY ||
|
|
70
|
+
process.env.FOLDERMD_API_KEY ||
|
|
71
|
+
null);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Resolve a usable folder.md key with ZERO user paste. Explicit/env/config key
|
|
75
|
+
* wins; otherwise auto-provision via the you.md backend (mints a scoped key
|
|
76
|
+
* server-to-server) and cache it locally. Returns the key + the media folderId
|
|
77
|
+
* when provisioning supplied one. Throws a friendly error if not authenticated
|
|
78
|
+
* or if provisioning is not configured on the backend.
|
|
79
|
+
*/
|
|
80
|
+
async function ensureProvisionedKey(explicit) {
|
|
81
|
+
const local = resolveFolderMdKey(explicit);
|
|
82
|
+
if (local) {
|
|
83
|
+
const cfg = (0, config_1.readGlobalConfig)();
|
|
84
|
+
return { apiKey: local, folderId: cfg.folderMdFolderId };
|
|
85
|
+
}
|
|
86
|
+
const res = await (0, api_1.provisionStorage)();
|
|
87
|
+
if (!res.ok || !res.data?.apiKey) {
|
|
88
|
+
const msg = (0, api_1.apiErrorMessage)(res.data) ||
|
|
89
|
+
(res.status === 401
|
|
90
|
+
? "not authenticated — run `you login` first"
|
|
91
|
+
: res.status === 503
|
|
92
|
+
? "folder.md storage isn't enabled on the server yet"
|
|
93
|
+
: "could not provision folder.md storage");
|
|
94
|
+
throw new Error(msg);
|
|
95
|
+
}
|
|
96
|
+
const cfg = (0, config_1.readGlobalConfig)();
|
|
97
|
+
cfg.folderMdKey = res.data.apiKey;
|
|
98
|
+
if (res.data.folderId)
|
|
99
|
+
cfg.folderMdFolderId = res.data.folderId;
|
|
100
|
+
(0, config_1.writeGlobalConfig)(cfg);
|
|
101
|
+
return { apiKey: res.data.apiKey, folderId: res.data.folderId || cfg.folderMdFolderId };
|
|
102
|
+
}
|
|
103
|
+
function baseUrl(auth) {
|
|
104
|
+
return (auth.baseUrl || process.env.FOLDERMD_BASE_URL || exports.FOLDERMD_DEFAULT_BASE).replace(/\/$/, "");
|
|
105
|
+
}
|
|
106
|
+
function authHeaders(auth) {
|
|
107
|
+
return { Authorization: `Bearer ${auth.apiKey}` };
|
|
108
|
+
}
|
|
109
|
+
async function asError(res) {
|
|
110
|
+
let body = "";
|
|
111
|
+
try {
|
|
112
|
+
body = await res.text();
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
/* ignore */
|
|
116
|
+
}
|
|
117
|
+
return new Error(`folder.md ${res.status} ${res.statusText}${body ? `: ${body.slice(0, 300)}` : ""}`);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Resolve the folder id to use for this user's media, creating + persisting a dedicated
|
|
121
|
+
* "you.md media" folder if none is configured. Shared by the CLI and the MCP tools so the
|
|
122
|
+
* "which folder?" logic lives in one place.
|
|
123
|
+
*/
|
|
124
|
+
async function ensureUserFolder(apiKey, explicit) {
|
|
125
|
+
const cfg = (0, config_1.readGlobalConfig)();
|
|
126
|
+
const existing = (explicit && explicit.trim()) || cfg.folderMdFolderId;
|
|
127
|
+
if (existing)
|
|
128
|
+
return existing;
|
|
129
|
+
const auth = { apiKey };
|
|
130
|
+
// Reuse an existing folder if present, else create one.
|
|
131
|
+
try {
|
|
132
|
+
const folders = (await listFolders(auth));
|
|
133
|
+
const mine = folders.find((f) => f.name === "you.md media") || folders[0];
|
|
134
|
+
if (mine?.id)
|
|
135
|
+
return persistFolderId(mine.id);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// listing failed — fall through to create
|
|
139
|
+
}
|
|
140
|
+
const created = await createFolder(auth, "you.md media");
|
|
141
|
+
if (!created.id)
|
|
142
|
+
throw new Error("folder.md did not return a folder id on create");
|
|
143
|
+
return persistFolderId(String(created.id));
|
|
144
|
+
}
|
|
145
|
+
function persistFolderId(id) {
|
|
146
|
+
const cfg = (0, config_1.readGlobalConfig)();
|
|
147
|
+
if (cfg.folderMdFolderId !== id) {
|
|
148
|
+
cfg.folderMdFolderId = id;
|
|
149
|
+
(0, config_1.writeGlobalConfig)(cfg);
|
|
150
|
+
}
|
|
151
|
+
return id;
|
|
152
|
+
}
|
|
153
|
+
/** List the user's folders (workspaces). */
|
|
154
|
+
async function listFolders(auth) {
|
|
155
|
+
const res = await fetch(`${baseUrl(auth)}/folders`, {
|
|
156
|
+
headers: authHeaders(auth),
|
|
157
|
+
signal: AbortSignal.timeout(30000),
|
|
158
|
+
});
|
|
159
|
+
if (!res.ok)
|
|
160
|
+
throw await asError(res);
|
|
161
|
+
const data = (await res.json());
|
|
162
|
+
return Array.isArray(data) ? data : data.folders ?? [];
|
|
163
|
+
}
|
|
164
|
+
/** Create a folder (workspace), e.g. a per-you.md-user media folder. */
|
|
165
|
+
async function createFolder(auth, name) {
|
|
166
|
+
const res = await fetch(`${baseUrl(auth)}/folders`, {
|
|
167
|
+
method: "POST",
|
|
168
|
+
headers: { ...authHeaders(auth), "Content-Type": "application/json" },
|
|
169
|
+
body: JSON.stringify({ name }),
|
|
170
|
+
signal: AbortSignal.timeout(30000),
|
|
171
|
+
});
|
|
172
|
+
if (!res.ok)
|
|
173
|
+
throw await asError(res);
|
|
174
|
+
return (await res.json());
|
|
175
|
+
}
|
|
176
|
+
/** List files in a folder. */
|
|
177
|
+
async function listFiles(auth, folderId) {
|
|
178
|
+
const res = await fetch(`${baseUrl(auth)}/folders/${encodeURIComponent(folderId)}/files`, {
|
|
179
|
+
headers: authHeaders(auth),
|
|
180
|
+
signal: AbortSignal.timeout(30000),
|
|
181
|
+
});
|
|
182
|
+
if (!res.ok)
|
|
183
|
+
throw await asError(res);
|
|
184
|
+
const data = (await res.json());
|
|
185
|
+
return Array.isArray(data) ? data : data.files ?? [];
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Upload a local file (binary or text) to a folder via multipart. This is the large-file/media
|
|
189
|
+
* path the brain cannot handle. Returns the created file record (store its `id` as the brain
|
|
190
|
+
* pointer). For text-only content the folder.md MCP `file_write` tool is an alternative.
|
|
191
|
+
*/
|
|
192
|
+
async function uploadFile(auth, folderId, localPath, options = {}) {
|
|
193
|
+
if (!fs.existsSync(localPath))
|
|
194
|
+
throw new Error(`file not found: ${localPath}`);
|
|
195
|
+
const bytes = fs.readFileSync(localPath);
|
|
196
|
+
const name = options.destPath || path.basename(localPath);
|
|
197
|
+
const blob = new Blob([bytes], options.mimeType ? { type: options.mimeType } : undefined);
|
|
198
|
+
const form = new FormData();
|
|
199
|
+
form.append("file", blob, name);
|
|
200
|
+
if (options.destPath)
|
|
201
|
+
form.append("path", options.destPath);
|
|
202
|
+
const res = await fetch(`${baseUrl(auth)}/folders/${encodeURIComponent(folderId)}/files`, {
|
|
203
|
+
method: "POST",
|
|
204
|
+
headers: authHeaders(auth), // do NOT set Content-Type; fetch sets the multipart boundary
|
|
205
|
+
body: form,
|
|
206
|
+
signal: AbortSignal.timeout(120000),
|
|
207
|
+
});
|
|
208
|
+
if (!res.ok)
|
|
209
|
+
throw await asError(res);
|
|
210
|
+
return (await res.json());
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Download a file's bytes. folder.md storage is private (no public CDN URL yet), so bytes come
|
|
214
|
+
* back through the authenticated API; you.md proxies/streams them rather than handing out a URL.
|
|
215
|
+
*/
|
|
216
|
+
async function downloadFile(auth, folderId, fileId, destPath) {
|
|
217
|
+
const url = `${baseUrl(auth)}/folders/${encodeURIComponent(folderId)}/files/${encodeURIComponent(fileId)}?download=true`;
|
|
218
|
+
const res = await fetch(url, { headers: authHeaders(auth), signal: AbortSignal.timeout(120000) });
|
|
219
|
+
if (!res.ok)
|
|
220
|
+
throw await asError(res);
|
|
221
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
222
|
+
fs.mkdirSync(path.dirname(path.resolve(destPath)), { recursive: true });
|
|
223
|
+
fs.writeFileSync(destPath, buf);
|
|
224
|
+
return { bytes: buf.length, path: destPath };
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=foldermd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"foldermd.js","sourceRoot":"","sources":["../../src/lib/foldermd.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export interface LoopTool {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
/** Human-readable parameter hints, e.g. { harness: "claude|codex|cursor", goal: "task text" }. */
|
|
5
|
+
parameters: Record<string, string>;
|
|
6
|
+
run: (args: Record<string, unknown>) => Promise<string>;
|
|
7
|
+
}
|
|
8
|
+
export interface LoopMessage {
|
|
9
|
+
role: "system" | "user" | "assistant";
|
|
10
|
+
content: string;
|
|
11
|
+
}
|
|
12
|
+
/** Injected model caller — returns the assistant's raw text for a message list. */
|
|
13
|
+
export type ModelCaller = (messages: LoopMessage[]) => Promise<string>;
|
|
14
|
+
export interface LoopStep {
|
|
15
|
+
index: number;
|
|
16
|
+
tool: string;
|
|
17
|
+
args: Record<string, unknown>;
|
|
18
|
+
result: string;
|
|
19
|
+
thought?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface RunLoopOptions {
|
|
22
|
+
goal: string;
|
|
23
|
+
tools: LoopTool[];
|
|
24
|
+
callModel: ModelCaller;
|
|
25
|
+
maxSteps?: number;
|
|
26
|
+
/** Extra orientation injected into the system prompt (identity, machine, fleet state). */
|
|
27
|
+
context?: string;
|
|
28
|
+
/** Progress callback per step (for spinner/log output). */
|
|
29
|
+
onStep?: (step: LoopStep) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Transient model-call failures to absorb before giving up on a step (real LLM APIs hiccup;
|
|
32
|
+
* one network blip shouldn't abandon the whole goal). Default 2 retries → 3 attempts total.
|
|
33
|
+
*/
|
|
34
|
+
maxModelRetries?: number;
|
|
35
|
+
/** Injectable sleep (so tests don't wait on real backoff). Defaults to setTimeout. */
|
|
36
|
+
sleep?: (ms: number) => Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
export interface LoopOutcome {
|
|
39
|
+
finished: boolean;
|
|
40
|
+
summary: string;
|
|
41
|
+
steps: LoopStep[];
|
|
42
|
+
}
|
|
43
|
+
interface ToolCall {
|
|
44
|
+
thought?: string;
|
|
45
|
+
tool: string;
|
|
46
|
+
args: Record<string, unknown>;
|
|
47
|
+
}
|
|
48
|
+
export declare function parseToolCall(raw: string): ToolCall | null;
|
|
49
|
+
/**
|
|
50
|
+
* Run the orchestrator loop until the model calls `finish` or maxSteps is hit.
|
|
51
|
+
* Each step: ask the model for a tool call → run it → append the result → repeat.
|
|
52
|
+
*/
|
|
53
|
+
export declare function runAgentLoop(options: RunLoopOptions): Promise<LoopOutcome>;
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=loop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../../src/lib/orchestrator/loop.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,kGAAkG;IAClG,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,mFAAmF;AACnF,MAAM,MAAM,WAAW,GAAG,CAAC,QAAQ,EAAE,WAAW,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAEvE,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,SAAS,EAAE,WAAW,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0FAA0F;IAC1F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IAClC;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sFAAsF;IACtF,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB;AAED,UAAU,QAAQ;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAyDD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAwD1D;AAMD;;;GAGG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC,CAkHhF"}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// loop.ts — the iterative agent loop for the You agent orchestrator.
|
|
3
|
+
//
|
|
4
|
+
// This is the one genuinely net-new piece the audit identified: every existing path in the
|
|
5
|
+
// CLI is single-hop (route -> run -> template-summarize -> stop). This closes the loop:
|
|
6
|
+
// the model picks a tool, we run it, feed the result back, and repeat until the model calls
|
|
7
|
+
// `finish`. It is model-agnostic (the model caller is injected) and tool-agnostic (tools are
|
|
8
|
+
// a registry of typed handlers), so the same loop drives orchestrator tools, brain tools, or
|
|
9
|
+
// remote-machine tools without change.
|
|
10
|
+
//
|
|
11
|
+
// The loop deliberately stays small and uses a JSON tool-call convention over plain chat
|
|
12
|
+
// completions, so it works against the existing /api/v1/chat proxy without needing native
|
|
13
|
+
// provider tool_use. That keeps the You agent harness/model-agnostic by construction.
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.parseToolCall = parseToolCall;
|
|
16
|
+
exports.runAgentLoop = runAgentLoop;
|
|
17
|
+
const FINISH_TOOL = "finish";
|
|
18
|
+
function buildSystemPrompt(goal, tools, context) {
|
|
19
|
+
const toolDocs = tools
|
|
20
|
+
.map((t) => {
|
|
21
|
+
const params = Object.entries(t.parameters)
|
|
22
|
+
.map(([k, v]) => ` - ${k}: ${v}`)
|
|
23
|
+
.join("\n");
|
|
24
|
+
return ` - ${t.name}: ${t.description}${params ? `\n${params}` : ""}`;
|
|
25
|
+
})
|
|
26
|
+
.join("\n");
|
|
27
|
+
return [
|
|
28
|
+
"You are U — Houston's personal master ORCHESTRATOR agent (U = the ultimate orchestrator, a role, not a person).",
|
|
29
|
+
"You do NOT write code or do the work yourself. You are model- and harness-agnostic: you",
|
|
30
|
+
"route work to the best worker agent (Claude Code for coding, etc.), launch and monitor",
|
|
31
|
+
"them across machines and projects, and report back. You are project-goal and task oriented.",
|
|
32
|
+
context ? `\nContext:\n${context}` : "",
|
|
33
|
+
"\nYou work in a loop. On EACH turn you call exactly one tool by replying with ONLY a JSON",
|
|
34
|
+
"object (no prose, no markdown fences) of the form:",
|
|
35
|
+
' {"thought": "<one short sentence of reasoning>", "tool": "<tool_name>", "args": { ... }}',
|
|
36
|
+
"\nAvailable tools:",
|
|
37
|
+
toolDocs,
|
|
38
|
+
` - ${FINISH_TOOL}: end the loop and report back. args: { summary: "what you did / found, for Houston" }`,
|
|
39
|
+
"\nRules:",
|
|
40
|
+
"- Reply with ONE json object and nothing else.",
|
|
41
|
+
"- Prefer delegating to a worker harness over doing work yourself.",
|
|
42
|
+
`- When the goal is met (or blocked), call ${FINISH_TOOL} with a clear summary.`,
|
|
43
|
+
`- Do not loop forever; if stuck, call ${FINISH_TOOL} and explain what is blocked.`,
|
|
44
|
+
`\nGoal: ${goal}`,
|
|
45
|
+
]
|
|
46
|
+
.filter(Boolean)
|
|
47
|
+
.join("\n");
|
|
48
|
+
}
|
|
49
|
+
function toolCallFromObject(obj) {
|
|
50
|
+
if (typeof obj.tool !== "string")
|
|
51
|
+
return null;
|
|
52
|
+
const args = obj.args && typeof obj.args === "object" ? obj.args : {};
|
|
53
|
+
return {
|
|
54
|
+
thought: typeof obj.thought === "string" ? obj.thought : undefined,
|
|
55
|
+
tool: obj.tool,
|
|
56
|
+
args,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Extract a tool call from a model reply, tolerating prose around/between JSON, code fences, and
|
|
61
|
+
* multiple objects. Scans for every balanced `{…}` span and returns the first that parses to an
|
|
62
|
+
* object with a string `tool` — so "Sure! {…prose…} {\"tool\":…}" still works (the naive
|
|
63
|
+
* first-`{`-to-last-`}` slice would fail on that).
|
|
64
|
+
*/
|
|
65
|
+
/** Reject absurdly large candidate JSON before parsing — a sane tool call is small, and a
|
|
66
|
+
* multi-hundred-KB span is either junk or a runaway response we don't want to parse/echo. */
|
|
67
|
+
const MAX_TOOL_CALL_CHARS = 256000;
|
|
68
|
+
function parseToolCall(raw) {
|
|
69
|
+
if (!raw)
|
|
70
|
+
return null;
|
|
71
|
+
let text = raw.trim();
|
|
72
|
+
const fence = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
73
|
+
if (fence)
|
|
74
|
+
text = fence[1].trim();
|
|
75
|
+
// Whole-string fast path.
|
|
76
|
+
if (text.length <= MAX_TOOL_CALL_CHARS) {
|
|
77
|
+
try {
|
|
78
|
+
const obj = JSON.parse(text);
|
|
79
|
+
const call = toolCallFromObject(obj);
|
|
80
|
+
if (call)
|
|
81
|
+
return call;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
/* fall through to span scan */
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Scan for balanced {…} spans (string-aware) and try each.
|
|
88
|
+
let depth = 0;
|
|
89
|
+
let startIdx = -1;
|
|
90
|
+
let inStr = false;
|
|
91
|
+
let escaped = false;
|
|
92
|
+
for (let i = 0; i < text.length; i++) {
|
|
93
|
+
const ch = text[i];
|
|
94
|
+
if (inStr) {
|
|
95
|
+
if (escaped)
|
|
96
|
+
escaped = false;
|
|
97
|
+
else if (ch === "\\")
|
|
98
|
+
escaped = true;
|
|
99
|
+
else if (ch === '"')
|
|
100
|
+
inStr = false;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (ch === '"')
|
|
104
|
+
inStr = true;
|
|
105
|
+
else if (ch === "{") {
|
|
106
|
+
if (depth === 0)
|
|
107
|
+
startIdx = i;
|
|
108
|
+
depth++;
|
|
109
|
+
}
|
|
110
|
+
else if (ch === "}") {
|
|
111
|
+
if (depth > 0) {
|
|
112
|
+
depth--;
|
|
113
|
+
if (depth === 0 && startIdx !== -1) {
|
|
114
|
+
const candidate = text.slice(startIdx, i + 1);
|
|
115
|
+
if (candidate.length > MAX_TOOL_CALL_CHARS) {
|
|
116
|
+
startIdx = -1;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const obj = JSON.parse(candidate);
|
|
121
|
+
const call = toolCallFromObject(obj);
|
|
122
|
+
if (call)
|
|
123
|
+
return call;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
/* try next span */
|
|
127
|
+
}
|
|
128
|
+
startIdx = -1;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
function truncate(s, max = 4000) {
|
|
136
|
+
return s.length > max ? s.slice(0, max) + `\n…[truncated ${s.length - max} chars]` : s;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Run the orchestrator loop until the model calls `finish` or maxSteps is hit.
|
|
140
|
+
* Each step: ask the model for a tool call → run it → append the result → repeat.
|
|
141
|
+
*/
|
|
142
|
+
async function runAgentLoop(options) {
|
|
143
|
+
const { goal, tools, callModel, context } = options;
|
|
144
|
+
const maxSteps = options.maxSteps ?? 12;
|
|
145
|
+
const maxModelRetries = options.maxModelRetries ?? 2;
|
|
146
|
+
const sleep = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
147
|
+
const toolByName = new Map(tools.map((t) => [t.name, t]));
|
|
148
|
+
// Absorb transient model-call failures (timeouts, 5xx, socket resets) with bounded
|
|
149
|
+
// exponential backoff. Real LLM/proxy endpoints hiccup; a single blip shouldn't abandon
|
|
150
|
+
// the whole goal mid-run. Throws the last error only after every attempt fails.
|
|
151
|
+
const callModelWithRetry = async (msgs) => {
|
|
152
|
+
let lastErr;
|
|
153
|
+
for (let attempt = 0; attempt <= maxModelRetries; attempt++) {
|
|
154
|
+
try {
|
|
155
|
+
return await callModel(msgs);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
lastErr = err;
|
|
159
|
+
if (attempt < maxModelRetries)
|
|
160
|
+
await sleep(Math.min(250 * 2 ** attempt, 4000));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
164
|
+
};
|
|
165
|
+
const messages = [
|
|
166
|
+
{ role: "system", content: buildSystemPrompt(goal, tools, context) },
|
|
167
|
+
{ role: "user", content: `Begin. Goal: ${goal}` },
|
|
168
|
+
];
|
|
169
|
+
const steps = [];
|
|
170
|
+
let consecutiveParseFailures = 0;
|
|
171
|
+
const MAX_PARSE_FAILURES = 2;
|
|
172
|
+
// Safety net for high maxSteps: each step appends 1-2 messages, so a long/stuck run could
|
|
173
|
+
// grow the context unboundedly. Stop cleanly well before that becomes a token problem.
|
|
174
|
+
const MAX_MESSAGES = 256;
|
|
175
|
+
for (let i = 0; i < maxSteps; i++) {
|
|
176
|
+
if (messages.length > MAX_MESSAGES) {
|
|
177
|
+
return {
|
|
178
|
+
finished: false,
|
|
179
|
+
summary: `Stopped: conversation grew past ${MAX_MESSAGES} messages without finishing (likely stuck). Narrow the goal or run again.`,
|
|
180
|
+
steps,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
let reply;
|
|
184
|
+
try {
|
|
185
|
+
reply = await callModelWithRetry(messages);
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
return {
|
|
189
|
+
finished: false,
|
|
190
|
+
summary: `Model call failed at step ${i + 1} after ${maxModelRetries + 1} attempt(s): ${err.message}`,
|
|
191
|
+
steps,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const call = parseToolCall(reply);
|
|
195
|
+
if (!call) {
|
|
196
|
+
// Model didn't emit a valid tool call — nudge, but bail after a couple of failures so a
|
|
197
|
+
// model that can't produce JSON doesn't silently burn the whole step budget.
|
|
198
|
+
consecutiveParseFailures++;
|
|
199
|
+
if (consecutiveParseFailures >= MAX_PARSE_FAILURES) {
|
|
200
|
+
return {
|
|
201
|
+
finished: false,
|
|
202
|
+
summary: `Stopped: the model did not return a valid tool call after ${MAX_PARSE_FAILURES} tries. Last reply: ${reply.slice(0, 200)}`,
|
|
203
|
+
steps,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
messages.push({ role: "assistant", content: reply });
|
|
207
|
+
messages.push({
|
|
208
|
+
role: "user",
|
|
209
|
+
content: 'That was not a valid tool call. Reply with ONLY a json object: {"thought":"...","tool":"...","args":{...}}.',
|
|
210
|
+
});
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
consecutiveParseFailures = 0;
|
|
214
|
+
messages.push({ role: "assistant", content: JSON.stringify(call) });
|
|
215
|
+
if (call.tool === FINISH_TOOL) {
|
|
216
|
+
const summary = typeof call.args.summary === "string" && call.args.summary.trim()
|
|
217
|
+
? call.args.summary
|
|
218
|
+
: "Done.";
|
|
219
|
+
return { finished: true, summary, steps };
|
|
220
|
+
}
|
|
221
|
+
const tool = toolByName.get(call.tool);
|
|
222
|
+
let result;
|
|
223
|
+
if (!tool) {
|
|
224
|
+
result = `error: unknown tool "${call.tool}". Available: ${tools.map((t) => t.name).join(", ")}, ${FINISH_TOOL}.`;
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
try {
|
|
228
|
+
result = await tool.run(call.args);
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
result = `error running ${call.tool}: ${err.message}`;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const step = { index: i, tool: call.tool, args: call.args, result, thought: call.thought };
|
|
235
|
+
steps.push(step);
|
|
236
|
+
options.onStep?.(step);
|
|
237
|
+
messages.push({
|
|
238
|
+
role: "user",
|
|
239
|
+
content: `Result of ${call.tool}:\n${truncate(result)}\n\nChoose the next tool (or ${FINISH_TOOL}).`,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
finished: false,
|
|
244
|
+
summary: `Reached the ${maxSteps}-step limit without finishing. Last activity is in the step log; consider a narrower goal or running again.`,
|
|
245
|
+
steps,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=loop.js.map
|