@proagentstore/cli 0.4.127 → 0.4.129

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.
@@ -129,7 +129,7 @@ export function parseApplyEnvelope(raw) {
129
129
  workspace: o.workspace,
130
130
  sources,
131
131
  artifacts,
132
- policy: { mode: p.mode, allowDomains, ...(p.mode === "auto_submit" && gateId ? { submitGate: { gateId } } : {}) },
132
+ policy: { mode: p.mode, allowDomains, ...(p.mode === "auto_submit" && gateId ? { submitGate: { gateId } } : {}), ...(p.screenshots === false ? { screenshots: false } : {}) },
133
133
  limits,
134
134
  };
135
135
  }
@@ -310,7 +310,8 @@ export class LocalApplyRuntime {
310
310
  token: randomBytes(24).toString("hex"),
311
311
  state: "running",
312
312
  events: [],
313
- shots: new ShotRecorder(APPLY_SHOT_CAP, () => this.now()),
313
+ // Screenshots off (#1064): zero cap for `/shots`, and no recorder handed to the bridge below.
314
+ shots: new ShotRecorder(envelope.policy.screenshots === false ? 0 : APPLY_SHOT_CAP, () => this.now()),
314
315
  seq: 0,
315
316
  allow: new Set(envelope.policy.allowDomains),
316
317
  grounding: [],
@@ -396,7 +397,7 @@ export class LocalApplyRuntime {
396
397
  limits: e.limits,
397
398
  mode: e.policy.mode,
398
399
  gateId: e.policy.submitGate?.gateId,
399
- }, run.shots);
400
+ }, e.policy.screenshots === false ? undefined : run.shots);
400
401
  if (spec.mcpConfig)
401
402
  writeFileSync(spec.mcpConfig.path, spec.mcpConfig.json, { mode: 0o600 });
402
403
  const child = this.spawn(spec.command, spec.args, { cwd: join(run.dir, "scratch"), env: spec.env, stdio: ["ignore", "pipe", "pipe"] });
@@ -28,6 +28,12 @@ export const LOCAL_ARTIFACT_ENGINES = ["claude", "codex"];
28
28
  export const LOCAL_ARTIFACT_AUTH_MODES = ["machine", "subscription"];
29
29
  export const LOCAL_ARTIFACT_SOURCE_ROLES = ["resume", "profile", "answers"];
30
30
  export const REQUIRED_SOURCE_ROLES = ["resume", "profile"];
31
+ /**
32
+ * The owner's saved answers to earlier blocking questions (#1066), sent by PAGS from the owner's
33
+ * Profile and given to the CLI as one more citable source under this role. Not a file source: it
34
+ * never appears in `sources`, `sourceHashes` or a path, and an older runner simply ignores it.
35
+ */
36
+ export const OWNER_ANSWERS_ROLE = "owner_answers";
31
37
  export const LOCAL_ARTIFACT_BLOCK_REASONS = [
32
38
  "missing_source",
33
39
  "malformed_lead",
@@ -52,7 +58,7 @@ export const LOCAL_ARTIFACT_EVENT_TYPES = [
52
58
  ];
53
59
  export const LOCAL_ARTIFACT_ENGINE_AUTHS = ["machine-login", "subscription", "api-key", "missing_login", "unknown"];
54
60
  export const LOCAL_ARTIFACT_PARSE_ATTEMPTS = ["fenced_json", "balanced_object"];
55
- export const LOCAL_ARTIFACT_CAPS = { questions: 20, questionChars: 300, sources: 6, sourceBytes: 256 * 1024 };
61
+ export const LOCAL_ARTIFACT_CAPS = { questions: 20, questionChars: 300, sources: 6, sourceBytes: 256 * 1024, ownerAnswers: 30, ownerAnswerChars: 500 };
56
62
  /** An id used as a folder name: no separators, no dots, nothing a path could be built out of. */
57
63
  export const PATH_SEGMENT = /^[A-Za-z0-9_-]{1,100}$/;
58
64
  /** A source path inside the workspace: relative, no `..`, no absolute, no NUL, no backslash. */
@@ -20,6 +20,7 @@
20
20
  import { mergeEnv } from "../coding/engine-env.js";
21
21
  import { resolveEngineAuth } from "../coding/engine-auth.js";
22
22
  import { finalText as finalTextFromEngine, missingLogin } from "../local-browser/engine.js";
23
+ import { OWNER_ANSWERS_ROLE } from "./contract.js";
23
24
  export { missingLogin };
24
25
  const PROVIDER_KEYS = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY"];
25
26
  const SUBSCRIPTION_TOKEN = "CLAUDE_CODE_OAUTH_TOKEN";
@@ -51,6 +52,10 @@ export function buildArtifactEngineSpec(input) {
51
52
  };
52
53
  }
53
54
  /** The brief. The rules are stated, but the checks in `checkDraft` are what enforce them. */
55
+ /** The owner's saved answers as one source text: a question and its answer per pair (#1066). */
56
+ export function ownerAnswersText(answers) {
57
+ return answers.map((a) => `Q: ${a.question}\nA: ${a.answer}`).join("\n\n");
58
+ }
54
59
  export function tailorPrompt(lead, sources) {
55
60
  const l = lead.lead;
56
61
  const job = [
@@ -78,6 +83,9 @@ export function tailorPrompt(lead, sources) {
78
83
  '- For EVERY factual claim about the owner in either document, add an entry to "claims" with the claim, the source role it comes from, and a short VERBATIM quote copied exactly from that source.',
79
84
  "- Every year, email address and phone number you write must appear in the sources.",
80
85
  "- Reword and reorder to fit the job; do not add what the sources do not say. Leave out what does not fit.",
86
+ ...(sources.some((x) => x.role === OWNER_ANSWERS_ROLE)
87
+ ? [`- <source role="${OWNER_ANSWERS_ROLE}"> holds the owner's own answers to questions asked on earlier applications. They are the owner's facts and preferences: use them, cite them like any source, and do not ask again a question they already answer.`]
88
+ : []),
81
89
  '- If something the application needs is missing or uncertain, do not guess: answer {"status":"needs_human","questions":["…"]} instead.',
82
90
  "",
83
91
  "Answer shape when you can do it:",
@@ -28,8 +28,8 @@ import { join, resolve, sep } from "node:path";
28
28
  import { RunnerInputError } from "../errors.js";
29
29
  import { redactText, secretEnvValues } from "../local-browser/contract.js";
30
30
  import { resolveWorkspacePath } from "../local-browser/runtime.js";
31
- import { LOCAL_ARTIFACT_AUTH_MODES, LOCAL_ARTIFACT_CAPS, LOCAL_ARTIFACT_ENGINES, LOCAL_ARTIFACT_SOURCE_ROLES, LOCAL_ARTIFACT_TASK_TYPE, PATH_SEGMENT, REQUIRED_SOURCE_ROLES, isHomeRelative, isWorkspaceRelative, parseLocalArtifactLead, } from "./contract.js";
32
- import { buildArtifactEngineSpec, checkDraft, finalText, missingLogin, observedArtifactAuth, parseDraft, tailorPrompt } from "./engine.js";
31
+ import { LOCAL_ARTIFACT_AUTH_MODES, LOCAL_ARTIFACT_CAPS, OWNER_ANSWERS_ROLE, LOCAL_ARTIFACT_ENGINES, LOCAL_ARTIFACT_SOURCE_ROLES, LOCAL_ARTIFACT_TASK_TYPE, PATH_SEGMENT, REQUIRED_SOURCE_ROLES, isHomeRelative, isWorkspaceRelative, parseLocalArtifactLead, } from "./contract.js";
32
+ import { buildArtifactEngineSpec, checkDraft, finalText, missingLogin, observedArtifactAuth, ownerAnswersText, parseDraft, tailorPrompt } from "./engine.js";
33
33
  /** The manifest in every generated folder — what retention reads, and proof the folder is ours. */
34
34
  export const ARTIFACT_MANIFEST = ".pags-artifact.json";
35
35
  const MAX_EVENTS = 200;
@@ -87,6 +87,18 @@ export function parseArtifactEnvelope(raw) {
87
87
  if (uploadedSources.length !== 2 || !uploadedSources.some((s) => s.role === "resume") || !uploadedSources.some((s) => s.role === "profile"))
88
88
  throw bad("uploaded sources need one resume and one profile");
89
89
  }
90
+ // The owner's saved answers to earlier questions (#1066): optional, bounded, plain text only.
91
+ const ownerAnswers = [];
92
+ if (o.ownerAnswers !== undefined) {
93
+ if (!Array.isArray(o.ownerAnswers) || o.ownerAnswers.length > LOCAL_ARTIFACT_CAPS.ownerAnswers)
94
+ throw bad(`ownerAnswers must be a list of at most ${LOCAL_ARTIFACT_CAPS.ownerAnswers}`);
95
+ for (const a of o.ownerAnswers) {
96
+ const r = (a && typeof a === "object" ? a : {});
97
+ if (typeof r.question !== "string" || !r.question.trim() || r.question.length > LOCAL_ARTIFACT_CAPS.questionChars || typeof r.answer !== "string" || !r.answer.trim() || r.answer.length > LOCAL_ARTIFACT_CAPS.ownerAnswerChars)
98
+ throw bad("each owner answer needs a question and an answer within their caps");
99
+ ownerAnswers.push({ question: r.question.trim(), answer: r.answer.trim() });
100
+ }
101
+ }
90
102
  const p = (o.policy && typeof o.policy === "object" ? o.policy : {});
91
103
  const retainDays = intIn(p.retainDays, 0, 3650);
92
104
  const maxMinutes = intIn(p.maxMinutes, 1, 60);
@@ -104,6 +116,7 @@ export function parseArtifactEnvelope(raw) {
104
116
  workspace: o.workspace,
105
117
  sources,
106
118
  ...(uploadedSources.length ? { uploadedSources } : {}),
119
+ ...(ownerAnswers.length ? { ownerAnswers } : {}),
107
120
  policy: { retainDays, maxMinutes, maxConcurrent },
108
121
  ...("lead" in lead ? { lead: lead.lead } : { lead: null, leadError: lead.error }),
109
122
  };
@@ -235,6 +248,12 @@ export class LocalArtifactRuntime {
235
248
  }
236
249
  if (missing.length)
237
250
  throw new Pause("missing_source", missing);
251
+ // The owner's saved answers go in as one more source the CLI may cite (#1066). Not a file, so no
252
+ // source hash or path; the count alone is recorded, never the text.
253
+ if (e.ownerAnswers?.length) {
254
+ texts.push({ role: OWNER_ANSWERS_ROLE, text: ownerAnswersText(e.ownerAnswers) });
255
+ this.emit(run, { type: "source.read", detail: { role: OWNER_ANSWERS_ROLE, count: e.ownerAnswers.length } });
256
+ }
238
257
  const outRel = `applications/${lead.leadId}/${e.runId}`;
239
258
  const outDir = join(workspace, "applications", lead.leadId, e.runId);
240
259
  try {
@@ -104,6 +104,7 @@ export function parseEnvelope(raw) {
104
104
  consentedDomains: strList(p.consentedDomains),
105
105
  profileConsented: p.profileConsented === true,
106
106
  consentIds: consentIdMap(p.consentIds),
107
+ ...(p.screenshots === false ? { screenshots: false } : {}),
107
108
  },
108
109
  limits,
109
110
  resultSchema: { id: typeof schema.id === "string" ? schema.id : "findings", version: typeof schema.version === "number" ? schema.version : 1 },
@@ -185,7 +186,9 @@ export class LocalBrowserRuntime {
185
186
  token: randomBytes(24).toString("hex"),
186
187
  state: "running",
187
188
  events: [],
188
- shots: new ShotRecorder(SCOUT_SHOT_CAP, () => this.now()),
189
+ // Screenshots off (#1064): a zero-cap recorder, so `/shots` answers with nothing — and the bridge
190
+ // below is given NO recorder at all, so no frame is ever attempted.
191
+ shots: new ShotRecorder(envelope.policy.screenshots === false ? 0 : SCOUT_SHOT_CAP, () => this.now()),
189
192
  seq: 0,
190
193
  consented: new Set(envelope.policy.consentedDomains),
191
194
  deny: new Set(envelope.policy.denyDomains),
@@ -230,7 +233,7 @@ export class LocalBrowserRuntime {
230
233
  allowListOnly: () => e.policy.allowDomains.length > 0,
231
234
  overTime: () => this.activeMs(run) >= e.limits.maxMinutes * 60_000,
232
235
  limits: e.limits,
233
- }, run.shots);
236
+ }, e.policy.screenshots === false ? undefined : run.shots);
234
237
  const bridgeScript = this.deps.bridgeScript ?? fileURLToPath(new URL("./bridge-stdio.js", import.meta.url));
235
238
  const spec = buildEngineSpec({
236
239
  engine: e.engine,
@@ -1109,7 +1109,7 @@ export class LocalRunner {
1109
1109
  url: active.url(),
1110
1110
  title: await active.title().catch(() => ""),
1111
1111
  challenge: await detectHumanChallenge(active),
1112
- screenshot: await this.shot(active),
1112
+ screenshot: action.screenshot === false ? undefined : await this.shot(active),
1113
1113
  commitGuard: { supported: true, mode: guard?.mode },
1114
1114
  };
1115
1115
  }
@@ -1143,7 +1143,7 @@ export class LocalRunner {
1143
1143
  title: await settled.title().catch(() => ""),
1144
1144
  challenge: await detectHumanChallenge(settled),
1145
1145
  feedback: dialogMsg ? `a native dialog was accepted: "${dialogMsg}"` : "a native dialog was accepted",
1146
- screenshot: await this.shot(settled),
1146
+ screenshot: action.screenshot === false ? undefined : await this.shot(settled),
1147
1147
  commitGuard: { supported: true, mode: guard?.mode },
1148
1148
  };
1149
1149
  }
@@ -1177,7 +1177,7 @@ export class LocalRunner {
1177
1177
  title: await active.title().catch(() => ""),
1178
1178
  challenge: await detectHumanChallenge(active),
1179
1179
  feedback: feedback || undefined,
1180
- screenshot: await this.shot(active),
1180
+ screenshot: action.screenshot === false ? undefined : await this.shot(active),
1181
1181
  commitGuard: { supported: true, mode: guard?.mode },
1182
1182
  };
1183
1183
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proagentstore/cli",
3
- "version": "0.4.127",
3
+ "version": "0.4.129",
4
4
  "description": "CLI for creating, publishing, and running ProAgentStore agents",
5
5
  "license": "MIT",
6
6
  "type": "module",