agentlas 1.0.66 → 1.0.67

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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.67 — 2026-08-30
4
+
5
+ - Cloud local state, source markers, and project credential imports now use
6
+ anchored no-follow atomic publication and fail closed on directory/file swaps
7
+ or unexpected successors.
8
+ - ACP JSON-RPC notifications no longer receive responses, while explicit
9
+ `id: null` requests retain normal request semantics.
10
+ - CLI build/MCP output now keeps JSON, YAML, quiet, header, and color modes
11
+ consistent and returns typed errors for contradictory machine-format input.
12
+ - Workforce candidate sets now require the exact WorkOrder ontology version,
13
+ and Experience packs reject duplicate MCP requirement identifiers before
14
+ they can persist invalid state.
15
+ - MCP consent is bound to the command, arguments, and relevant environment
16
+ material at approval time, while Hub installs recheck managed parents and
17
+ publish atomically without following path swaps.
18
+
3
19
  ## 1.0.66 — 2026-08-30
4
20
 
5
21
  - API 스트리밍 요청이 연결·본문 읽기 중 실패하거나 취소돼도 부모 취소 리스너와 idle 타이머를 즉시 정리해, 오래 실행한 Terminal 세션에 실패한 요청의 리스너가 누적되지 않게 했습니다.
package/README.md CHANGED
@@ -17,6 +17,12 @@ Use this existing independent Terminal as the command-line runtime: inspect owne
17
17
 
18
18
  Agent Trust is our product principle: agent packages are treated as portable, owner-scoped, inspectable, and restorable assets. (It does not imply regulated financial or fiduciary services.) Agent Cloud stores package assets in your private cloud account, while Agentlas Terminal verifies and executes local runtime copies on your host machine.
19
19
 
20
+ Current release: 1.0.67. It hardens local Cloud and credential storage against
21
+ path races, keeps ACP notifications protocol-correct, aligns CLI machine output
22
+ and typed errors, binds Workforce selections to exact ontology versions,
23
+ rejects duplicate Experience requirements, and makes MCP consent and Hub
24
+ installs race-safe.
25
+
20
26
  ---
21
27
 
22
28
  ## Requirements
package/bin/agentlas.cjs CHANGED
@@ -72,7 +72,7 @@ function dbPath() {
72
72
  // Keep the launcher's no-database path identical to the engine's CLI grammar.
73
73
  // A plain word "help" is valid task content (`agentlas run -p help`); only the
74
74
  // actual help/version command forms may skip first-run database bootstrap.
75
- function isMetadataOnlyInvocation(argv) {
75
+ function isMetadataOnlyInvocation(argv, parsedOutput) {
76
76
  const args = Array.isArray(argv) ? argv : [];
77
77
  const helpRequested = args.some((arg) => arg === "--help" || arg === "-h");
78
78
  const helpCommand = args.find((arg) => arg !== "--help" && arg !== "-h" && !String(arg).startsWith("-"));
@@ -83,7 +83,7 @@ function isMetadataOnlyInvocation(argv) {
83
83
  if (arg === "--version" || arg === "-V") return "version";
84
84
  return arg;
85
85
  });
86
- const { rest } = parseOutputFlags(normalized);
86
+ const { rest } = parsedOutput || parseOutputFlags(normalized);
87
87
  const [rawCommand, ...commandArgs] = rest;
88
88
  if (rawCommand === "help" || rawCommand === "version") return true;
89
89
  return SELF_HELP_COMMANDS.has(resolveCommandName(rawCommand)) && commandArgs[0] === "help";
@@ -556,8 +556,21 @@ function bootstrapDbIfMissing() {
556
556
 
557
557
  function main() {
558
558
  const args = process.argv.slice(2);
559
- const { options: outputOptions } = parseOutputFlags(args);
560
- const metadataOnly = isMetadataOnlyInvocation(args);
559
+ const normalized = args.map((arg) => {
560
+ if (arg === "--help" || arg === "-h") return "help";
561
+ if (arg === "--version" || arg === "-V") return "version";
562
+ return arg;
563
+ });
564
+ let parsedOutput;
565
+ try {
566
+ parsedOutput = parseOutputFlags(normalized);
567
+ } catch (error) {
568
+ process.stderr.write(renderError(error, { format: "table", noColor: true }) + "\n");
569
+ process.exitCode = 1;
570
+ return;
571
+ }
572
+ const { options: outputOptions } = parsedOutput;
573
+ const metadataOnly = isMetadataOnlyInvocation(args, parsedOutput);
561
574
  const sqliteDriver = probeSqliteDriver();
562
575
  const engineFound = exists(ENGINE);
563
576
 
@@ -170,8 +170,13 @@ class AcpAgentServer {
170
170
  this.output.write(JSON.stringify(obj) + "\n");
171
171
  }
172
172
  notify(method, params) { this.send({ jsonrpc: "2.0", method, params }); }
173
- reply(id, result) { this.send({ jsonrpc: "2.0", id, result }); }
174
- error(id, code, message, data) { this.send({ jsonrpc: "2.0", id, error: { code, message, ...(data !== undefined ? { data } : {}) } }); }
173
+ // JSON-RPC notifications have no response channel. Keep `id: null` as a
174
+ // request (the protocol distinguishes an absent id from an explicit null).
175
+ reply(id, result) { if (id === undefined) return; this.send({ jsonrpc: "2.0", id, result }); }
176
+ error(id, code, message, data) {
177
+ if (id === undefined) return;
178
+ this.send({ jsonrpc: "2.0", id, error: { code, message, ...(data !== undefined ? { data } : {}) } });
179
+ }
175
180
 
176
181
  start() {
177
182
  const rl = readline.createInterface({ input: this.input, crlfDelay: Infinity });
@@ -1111,6 +1111,7 @@ function validateCandidateSet(value, workOrder, now = new Date(), options = {})
1111
1111
  assertId(set.selectionSessionId, "candidateSet.selectionSessionId");
1112
1112
  if (set.workOrderId !== workOrder.workOrderId) fail("candidate_set_invalid", "candidate set workOrderId mismatch");
1113
1113
  assertId(set.ontologyVersion, "candidateSet.ontologyVersion");
1114
+ if (set.ontologyVersion !== workOrder.ontologyVersion) fail("candidate_set_invalid", "candidate set ontologyVersion mismatch");
1114
1115
  assertHash(set.candidateSetDigest, "candidateSet.candidateSetDigest");
1115
1116
  if (set.decisionOwner !== "host_llm") fail("candidate_set_invalid", "Hub candidate set tried to take selection authority");
1116
1117
  if (set.historyInfluence !== "none") fail("candidate_set_invalid", "history/popularity influenced candidate retrieval");
@@ -189,7 +189,17 @@ function main() {
189
189
 
190
190
  // 전역 출력 플래그는 명령에 닿기 전에 한 곳에서 뜯어낸다 —
191
191
  // 명령마다 --json 유무가 갈리던 것을 구조로 막는다.
192
- const { options: outputOptions, rest: commandArgv } = parseOutputFlags(normalized);
192
+ // --json + --yaml 모순된 요청이라 JSON/YAML 중 하나를 임의로 고르지 않고,
193
+ // 진입점에서 결정적인 평문 INVALID_ARGUMENT 로 멈춘다.
194
+ let parsedOutput;
195
+ try {
196
+ parsedOutput = parseOutputFlags(normalized);
197
+ } catch (error) {
198
+ process.stderr.write(renderError(error, { format: "table", noColor: true }) + "\n");
199
+ process.exitCode = 1;
200
+ return;
201
+ }
202
+ const { options: outputOptions, rest: commandArgv } = parsedOutput;
193
203
  const ctx = buildCtx();
194
204
  ctx.output = outputOptions;
195
205
  let code;
@@ -276,19 +276,33 @@ function renderError(error, options = {}) {
276
276
  function parseOutputFlags(argv, env = process.env, isTty = Boolean(process.stdout.isTTY)) {
277
277
  const rest = [];
278
278
  const options = { ...DEFAULT_OPTIONS };
279
+ let sawJson = false;
280
+ let sawYaml = false;
279
281
  for (let index = 0; index < argv.length; index += 1) {
280
282
  const token = argv[index];
281
283
  if (token === "--") {
282
284
  rest.push(...argv.slice(index + 1));
283
285
  break;
284
286
  }
285
- if (token === "--json") options.format = "json";
286
- else if (token === "--yaml") options.format = "yaml";
287
+ if (token === "--json") {
288
+ sawJson = true;
289
+ options.format = "json";
290
+ }
291
+ else if (token === "--yaml") {
292
+ sawYaml = true;
293
+ options.format = "yaml";
294
+ }
287
295
  else if (token === "--quiet" || token === "-q") options.quiet = true;
288
296
  else if (token === "--no-headers") options.noHeaders = true;
289
297
  else if (token === "--no-color") options.noColor = true;
290
298
  else rest.push(token);
291
299
  }
300
+ if (sawJson && sawYaml) {
301
+ const error = new Error("--json and --yaml cannot be used together");
302
+ error.code = "INVALID_ARGUMENT";
303
+ error.details = { flags: ["--json", "--yaml"] };
304
+ throw error;
305
+ }
292
306
  if (env && (env.NO_COLOR || env.AGENTLAS_NO_COLOR)) options.noColor = true;
293
307
  if (!isTty) options.noColor = true;
294
308
  return { options, rest };