@proagentstore/cli 0.4.92 → 0.4.93

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.
@@ -20,6 +20,8 @@ export const LOCAL_ARTIFACT_TASK_TYPE = "local_artifact.generate";
20
20
  export const LOCAL_ARTIFACT_RUN_PATH = "/local-artifact/run";
21
21
  export const LOCAL_ARTIFACT_STATUS_PATH = "/local-artifact/status";
22
22
  export const LOCAL_ARTIFACT_CANCEL_PATH = "/local-artifact/cancel";
23
+ /** First CLI that consumes an explicit uploaded source instead of its local master files. */
24
+ export const LOCAL_ARTIFACT_UPLOADED_SOURCES_MIN_CLI = "0.4.93";
23
25
  /** The readiness event a completed tailoring run emits, once, through the connection outbox. */
24
26
  export const MATERIALS_READY_EVENT = "job.application.materials_ready";
25
27
  export const LOCAL_ARTIFACT_ENGINES = ["claude", "codex"];
@@ -67,6 +67,26 @@ export function parseArtifactEnvelope(raw) {
67
67
  throw bad(`source ${String(r.role)} is listed twice`);
68
68
  sources.push({ role: r.role, path: r.path });
69
69
  }
70
+ const uploadedSources = [];
71
+ if (o.uploadedSources !== undefined) {
72
+ if (!Array.isArray(o.uploadedSources) || o.uploadedSources.length !== 2)
73
+ throw bad("uploadedSources must contain the selected resume and profile");
74
+ for (const source of o.uploadedSources) {
75
+ const r = (source && typeof source === "object" ? source : {});
76
+ if ((r.role !== "resume" && r.role !== "profile") || uploadedSources.some((x) => x.role === r.role))
77
+ throw bad("uploaded sources need one resume and one profile");
78
+ if (typeof r.fileId !== "string" || !PATH_SEGMENT.test(r.fileId) || typeof r.version !== "string" || !r.version || r.version.length > 300 || typeof r.originalSha256 !== "string" || !/^[a-f0-9]{64}$/.test(r.originalSha256) || typeof r.extractedTextSha256 !== "string" || !/^[a-f0-9]{64}$/.test(r.extractedTextSha256) || typeof r.extractedAt !== "string" || Number.isNaN(Date.parse(r.extractedAt)) || typeof r.text !== "string")
79
+ throw bad("uploaded source provenance is invalid");
80
+ const bytes = Buffer.byteLength(r.text, "utf8");
81
+ if (!bytes || bytes > LOCAL_ARTIFACT_CAPS.sourceBytes)
82
+ throw bad(`uploaded source must be 1-${LOCAL_ARTIFACT_CAPS.sourceBytes} UTF-8 bytes`);
83
+ if (sha256(r.text) !== r.extractedTextSha256)
84
+ throw bad("uploaded source text does not match its extracted-text hash");
85
+ uploadedSources.push({ role: r.role, fileId: r.fileId, version: r.version, originalSha256: r.originalSha256, extractedTextSha256: r.extractedTextSha256, extractedAt: r.extractedAt, text: r.text });
86
+ }
87
+ if (uploadedSources.length !== 2 || !uploadedSources.some((s) => s.role === "resume") || !uploadedSources.some((s) => s.role === "profile"))
88
+ throw bad("uploaded sources need one resume and one profile");
89
+ }
70
90
  const p = (o.policy && typeof o.policy === "object" ? o.policy : {});
71
91
  const retainDays = intIn(p.retainDays, 0, 3650);
72
92
  const maxMinutes = intIn(p.maxMinutes, 1, 60);
@@ -83,6 +103,7 @@ export function parseArtifactEnvelope(raw) {
83
103
  authMode: o.authMode,
84
104
  workspace: o.workspace,
85
105
  sources,
106
+ ...(uploadedSources.length ? { uploadedSources } : {}),
86
107
  policy: { retainDays, maxMinutes, maxConcurrent },
87
108
  ...("lead" in lead ? { lead: lead.lead } : { lead: null, leadError: lead.error }),
88
109
  };
@@ -163,31 +184,53 @@ export class LocalArtifactRuntime {
163
184
  throw new Pause("workspace_unavailable", [`The workspace ${e.workspace} cannot be used: ${err instanceof Error ? err.message : String(err)}`]);
164
185
  }
165
186
  this.sweepArtifacts(run, workspace);
166
- // Sources: read by the runtime, hashed, handed to the CLI in its prompt. Content never leaves this function except into the prompt.
187
+ // Sources: uploaded selections arrive only over the authenticated relay, are written beneath
188
+ // this run's private scratch folder under fixed role names, and never use a user filename/path.
189
+ // Local mode remains the existing path. Content never reaches events/results/DB.
167
190
  const texts = [];
168
191
  const hashes = [];
169
192
  const missing = [];
170
- for (const s of e.sources) {
171
- const shown = `${e.workspace}/${s.path}`;
172
- try {
173
- const file = resolveSource(workspace, s.path);
174
- const buf = readFileSync(file);
175
- if (buf.length > LOCAL_ARTIFACT_CAPS.sourceBytes)
176
- throw new Error(`larger than ${LOCAL_ARTIFACT_CAPS.sourceBytes / 1024} KB`);
177
- if (!buf.length)
178
- throw new Error("empty");
179
- texts.push({ role: s.role, text: buf.toString("utf8") });
180
- const hash = { role: s.role, path: shown, sha256: sha256(buf), bytes: buf.length };
181
- hashes.push(hash);
182
- this.emit(run, { type: "source.read", detail: hash });
183
- }
184
- catch (err) {
185
- this.emit(run, { type: "source.missing", detail: { role: s.role, path: shown } });
186
- missing.push(`Your ${s.role} at ${shown} could not be read (${err instanceof Error && !/ENOENT/.test(err.message) ? err.message : "not found"}). Add it, or choose another file in the Application Tailor settings.`);
193
+ if (e.uploadedSources?.length) {
194
+ const sourceDir = join(run.scratch, "sources");
195
+ mkdirSync(sourceDir, { recursive: true, mode: 0o700 });
196
+ for (const s of e.uploadedSources) {
197
+ const safePath = join(sourceDir, `${s.role}.txt`);
198
+ try {
199
+ writeFileSync(safePath, s.text, { encoding: "utf8", flag: "wx", mode: 0o600 });
200
+ const bytes = Buffer.byteLength(s.text, "utf8");
201
+ const hash = { role: s.role, path: `uploaded/${s.fileId}@${s.version}`, sha256: s.extractedTextSha256, bytes };
202
+ texts.push({ role: s.role, text: readFileSync(safePath, "utf8") });
203
+ hashes.push(hash);
204
+ this.emit(run, { type: "source.read", detail: hash });
205
+ }
206
+ catch (err) {
207
+ this.emit(run, { type: "source.missing", detail: { role: s.role, path: `uploaded/${s.fileId}` } });
208
+ missing.push(`The selected uploaded ${s.role} could not be materialized (${err instanceof Error ? err.message : "unknown error"}).`);
209
+ }
187
210
  }
188
211
  }
212
+ else
213
+ for (const s of e.sources) {
214
+ const shown = `${e.workspace}/${s.path}`;
215
+ try {
216
+ const file = resolveSource(workspace, s.path);
217
+ const buf = readFileSync(file);
218
+ if (buf.length > LOCAL_ARTIFACT_CAPS.sourceBytes)
219
+ throw new Error(`larger than ${LOCAL_ARTIFACT_CAPS.sourceBytes / 1024} KB`);
220
+ if (!buf.length)
221
+ throw new Error("empty");
222
+ texts.push({ role: s.role, text: buf.toString("utf8") });
223
+ const hash = { role: s.role, path: shown, sha256: sha256(buf), bytes: buf.length };
224
+ hashes.push(hash);
225
+ this.emit(run, { type: "source.read", detail: hash });
226
+ }
227
+ catch (err) {
228
+ this.emit(run, { type: "source.missing", detail: { role: s.role, path: shown } });
229
+ missing.push(`Your ${s.role} at ${shown} could not be read (${err instanceof Error && !/ENOENT/.test(err.message) ? err.message : "not found"}). Add it, or choose another file in the Application Tailor settings.`);
230
+ }
231
+ }
189
232
  for (const role of REQUIRED_SOURCE_ROLES) {
190
- if (!e.sources.some((s) => s.role === role))
233
+ if (e.uploadedSources?.length ? !e.uploadedSources.some((s) => s.role === role) : !e.sources.some((s) => s.role === role))
191
234
  missing.push(`No ${role} source is configured. Choose one in the Application Tailor settings.`);
192
235
  }
193
236
  if (missing.length)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proagentstore/cli",
3
- "version": "0.4.92",
3
+ "version": "0.4.93",
4
4
  "description": "CLI for creating, publishing, and running ProAgentStore agents",
5
5
  "license": "MIT",
6
6
  "type": "module",