agentlas 1.0.15 → 1.0.17

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,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.17 — 2026-07-31
4
+
5
+ - **Terminal requires the verification-aware Context Map engine.** Context
6
+ commands now select Agentlas OS 1.1.86 or newer, whose impact graph connects
7
+ code changes to tests, test commands, CI workflows, and product-version
8
+ contracts, including local test files intentionally excluded from Git.
9
+ - A stale Core can no longer satisfy the Terminal context capability merely by
10
+ exposing the old module path; the version gate fails closed before execution.
11
+
12
+ ## 1.0.16 — 2026-07-30
13
+
14
+ - **Project-scoped plugin listing now fails closed.** A missing, unsafe, or
15
+ uninitialized `--project` path returns a clear error instead of silently
16
+ showing the global Hub catalog as if project compatibility had been checked.
17
+ - Plugin listing names its actual scope: the global Hub catalog does not claim
18
+ to evaluate project compatibility or local installation state.
19
+ - Unknown, conflicting, or incomplete plugin flags now return usage errors
20
+ instead of being ignored.
21
+
3
22
  ## 1.0.15 — 2026-07-29
4
23
 
5
24
  - **Agent Cloud saves work from a new machine or fresh clone.** Before writing,
@@ -10,7 +10,7 @@ const { compareSemVer, normalizeSemVer } = require("./semver.cjs");
10
10
  const HARNESS_SCHEMA_VERSION = "agentlas.stormbreaker.goal-ultracode-harness.v1";
11
11
  const HARNESS_ID = "agentlas-core/stormbreaker-goal-ultracode";
12
12
  const HARNESS_MODE = "stormbreaker-goal-ultracode";
13
- const CONTEXT_MAP_MIN_CORE_VERSION = "1.1.66";
13
+ const CONTEXT_MAP_MIN_CORE_VERSION = "1.1.86";
14
14
  const CORE_MANIFEST_MAX_BYTES = 64 * 1024;
15
15
  const CORE_RUNTIME_MARKERS = [
16
16
  ["agentlas_cloud", "__main__.py"],
@@ -142,13 +142,14 @@ const HUB_SECRET_PATTERNS = [
142
142
  ["credential_assignment", /\b(?:api[_-]?key|access[_-]?key|client[_-]?secret|secret|token|password|passwd|cookie)\s*[:=]\s*['"]?[^\s'";,]{8,}/i],
143
143
  ["credential_url", /\b[A-Za-z][A-Za-z0-9+.-]*:\/\/[^\s/:@]+:[^\s/@]+@[^\s/]+/i],
144
144
  ];
145
- const WORKFORCE_ONTOLOGY_MENU = [
146
- "Controlled communities: community:software-engineering, community:backend-engineering, community:frontend-engineering, community:database-engineering, community:payments-engineering, community:quality-engineering, community:security-engineering, community:data-engineering, community:ai-engineering, community:devops, community:product-design, community:research, community:marketing, community:finance, community:corporate-development, community:insurance, community:insurance-actuarial, community:insurance-claims, community:insurance-underwriting, community:human-resources, community:information-technology, community:legal, community:travel, community:operations, community:agent-systems.",
147
- "Controlled roles: role:software-architect, role:backend-engineer, role:frontend-engineer, role:database-engineer, role:payments-engineer, role:quality-engineer, role:security-engineer, role:ontology-architect, role:agent-runtime-engineer, role:researcher, role:ma-diligence-lead, role:insurance-actuary, role:claims-diligence-specialist, role:underwriting-diligence-specialist, role:travel-planner.",
148
- "Canonical skills: skill:software-architecture, skill:api-design, skill:server-implementation, skill:frontend-implementation, skill:data-modeling, skill:database-querying, skill:billing-integration, skill:transaction-integrity, skill:test-design, skill:verification, skill:security-review, skill:ontology-modeling, skill:knowledge-graph-design, skill:multi-agent-orchestration, skill:runtime-integration, skill:evidence-synthesis, skill:deal-diligence, skill:valuation, skill:actuarial-reserving, skill:solvency-analysis, skill:claims-liability-assessment, skill:underwriting-portfolio-analysis, skill:travel-planning.",
149
- "Canonical tool capabilities: tool:file-system, tool:file-read, tool:file-write, tool:shell, tool:web-search, tool:browser, tool:mongodb, tool:database, tool:github, tool:payments.",
150
- "Use artifact:<kind> for consumes, produces and edge artifactKinds. consumes and produces are hard candidate-profile declaration gates: list an artifact there only when the Hub package itself must declare that exact input/output capability. Put ordinary workflow inputs, outputs, and handoffs in the slot task and edges.artifactKinds instead. Default requiredRoles to an empty array. There is no optionalRoles field: express desired role fit through title, task, optionalCommunities, and optionalSkills. Require an exact controlled role only when a candidate lacking that exact declared role could not execute the assignment; never invent a near-synonym role ID.",
151
- "Treat required roles, skills, tools, artifacts and authorities as non-negotiable hard constraints only when Hub package declarations must prove them. Legacy Hub profiles can legitimately have empty role/tool fields. Use a broad required community for the job-family boundary, put desired expertise in optional communities/skills plus the role task, and let the host LLM judge title, summary and semantic evidence.",
145
+ const WORKFORCE_ONTOLOGY_GUIDE = [
146
+ "Roles, communities, skills and knowledge are open-world English semantic IDs. Seed values are aliases and graph anchors, never allowlists. Author a new faithful namespaced ID when no seed expresses the work.",
147
+ "Community seed examples: community:software-engineering, community:backend-engineering, community:frontend-engineering, community:database-engineering, community:payments-engineering, community:quality-engineering, community:security-engineering, community:data-engineering, community:ai-engineering, community:devops, community:product-design, community:research, community:marketing, community:finance, community:legal, community:travel, community:operations, community:agent-systems.",
148
+ "Role seed examples: role:software-architect, role:backend-engineer, role:frontend-engineer, role:database-engineer, role:payments-engineer, role:quality-engineer, role:security-engineer, role:ontology-architect, role:agent-runtime-engineer, role:researcher, role:travel-planner.",
149
+ "Skill seed examples: skill:software-architecture, skill:api-design, skill:server-implementation, skill:frontend-implementation, skill:data-modeling, skill:test-design, skill:verification, skill:ontology-modeling, skill:knowledge-graph-design, skill:evidence-synthesis, skill:travel-planning.",
150
+ "Tool capability seed examples: tool:file-system, tool:file-read, tool:file-write, tool:shell, tool:web-search, tool:browser, tool:mongodb, tool:database, tool:github, tool:payments. Tool binding remains a prepare-time runtime concern.",
151
+ "Use artifact:<kind> only for edges.artifactKinds. Do not fill consumes, produces, requiredRoles, requiredToolCapabilities, requiredAuthorities, forbiddenAuthorities, or modalities on a slot tools, authorities, and modalities attach to the executing runtime, not the agent card, and card-declaration gates on those fields only exclude real candidates. Put ordinary workflow inputs, outputs, and handoffs in the slot task and edges.artifactKinds. Express desired role fit through title, task, optionalCommunities, and optionalSkills.",
152
+ "Use a broad required community for the job-family boundary, put desired expertise in optional communities/skills plus the role task, and let the host LLM judge title, summary and semantic evidence.",
152
153
  "forbiddenCommunities and excludedCommunities are not exhaustive lists of every unused job family. Add only an explicit user prohibition or an inherent incompatibility with the assignment. Never forbid a broad ancestor, descendant, adjacent, or legitimately co-occurring community merely because another community was selected.",
153
154
  ].join("\n");
154
155
 
@@ -1013,6 +1014,23 @@ function validateWorkOrder(value) {
1013
1014
  }
1014
1015
  const slots = assertArray(order.roleSlots, "workOrder.roleSlots", MAX_SLOTS, { min: 1 });
1015
1016
  const seen = new Set();
1017
+ // An absent list-valued slot field IS the empty constraint (2026-07-30):
1018
+ // normalize (absent -> []) BEFORE validation and before the order is
1019
+ // digested or sent, so a lean-form author and a full-form author produce
1020
+ // byte-identical canonical orders. Full forms pass through untouched.
1021
+ const SLOT_LIST_FIELDS = [
1022
+ "requiredCommunities", "optionalCommunities", "excludedCommunities",
1023
+ "requiredRoles", "requiredSkills", "optionalSkills", "requiredKnowledge",
1024
+ "requiredToolCapabilities", "consumes", "produces", "requiredAuthorities",
1025
+ "forbiddenAuthorities", "runtimes", "languages", "modalities",
1026
+ ];
1027
+ for (let index = 0; index < slots.length; index += 1) {
1028
+ if (slots[index] && typeof slots[index] === "object" && !Array.isArray(slots[index])) {
1029
+ for (const field of SLOT_LIST_FIELDS) {
1030
+ if (!Object.prototype.hasOwnProperty.call(slots[index], field)) slots[index][field] = [];
1031
+ }
1032
+ }
1033
+ }
1016
1034
  for (let index = 0; index < slots.length; index += 1) {
1017
1035
  const slot = assertObject(slots[index], `roleSlots[${index}]`);
1018
1036
  assertExactKeys(slot, [
@@ -1850,14 +1868,14 @@ function buildPrompts(task, identity) {
1850
1868
  "Return the direct agentlas.workforce-work-order.v1 JSON object. Do not emit schemaVersion=agentlas.workforce-leader-call.v1 and do not emit toolCall, name, or arguments wrappers. The host invokes workforce.search_candidates with your exact validated WorkOrder.",
1851
1869
  "The direct WorkOrder top level must contain exactly: schemaVersion, workOrderId, taskBrief, redacted, ontologyVersion, roleSlots, edges, forbiddenCommunities, selectionPolicy.",
1852
1870
  `Exact direct WorkOrder example: ${stableJson(workOrderShape)}`,
1853
- "Every roleSlots item must contain exactly slotId, title, task, cardinality, criticality, requiredCommunities, optionalCommunities, excludedCommunities, requiredRoles, requiredSkills, optionalSkills, requiredKnowledge, requiredToolCapabilities, consumes, produces, requiredAuthorities, forbiddenAuthorities, runtimes, languages, modalities, and allowedEntityKinds; minimumEvidenceLevel is the only optional extra key. Empty arrays must still be present; the host will not add them.",
1854
- "consumes and produces are hard eligibility fields matched against exact candidate-profile declarations. Do not use them for ordinary project workflow. Describe normal inputs/outputs in task and represent inter-slot handoffs with edges and edges.artifactKinds.",
1871
+ "Every roleSlots item must contain slotId, title, task, cardinality, criticality, and allowedEntityKinds; minimumEvidenceLevel is optional. Constrain the hire only through requiredCommunities, optionalCommunities, excludedCommunities, requiredSkills, optionalSkills, requiredKnowledge, runtimes, and languages, and only when the constraint is genuine. Any list field you leave out is the empty constraint the host normalizes absent to [] before validation and digest, so omit empty ones instead of spelling [].",
1872
+ "Never fill requiredToolCapabilities, requiredAuthorities, forbiddenAuthorities, consumes, produces, requiredRoles, or modalities: tools, authorities, and modalities attach to the executing runtime, not the agent card, and those gates only exclude real candidates. Describe normal inputs/outputs in task and represent inter-slot handoffs with edges and edges.artifactKinds.",
1855
1873
  "workOrderId and every concept/reference id must match [A-Za-z0-9][A-Za-z0-9._:/@-]{1,255} and have total length at most 255 characters. taskBrief is limited to 64000 characters; each slot title to 160 and slot task to 32000 — describe each responsibility as fully as the work honestly needs. Each id array is limited to 256 unique items.",
1856
1874
  "roleSlots must contain 1-32 items. cardinality must be an integer from 1 through 16. criticality must be exactly required or optional. allowedEntityKinds must be a non-empty unique subset of executable agent, team. group is ontology/discovery metadata and cannot be executed. minimumEvidenceLevel, when authored, must be exactly declared, checked, demonstrated, or attested.",
1857
1875
  "edges must contain at most 128 items. Every edge must contain exactly from, to, relation, and artifactKinds. from and to must reference declared slotId values. relation must be exactly one of reportsTo, handsOffTo, reviews, coordinatesWith.",
1858
1876
  "forbiddenCommunities and edges must be explicitly authored arrays. selectionPolicy must contain exactly allowHistoryEvidence=false, integer minimumCandidatesPerSlot from 2 through 30, and integer maximumCandidatesPerSlot from 2 through 100 that is not below the minimum.",
1859
1877
  "A community cannot appear in forbiddenCommunities or a slot's excludedCommunities when that same slot requires or optionally prefers it. Also avoid broader ancestor, descendant, adjacent, and legitimately co-occurring exclusions; the host rejects exact contradictions but does not invent ontology lineage or mutate your decision.",
1860
- `redacted must be true and ontologyVersion must be exactly ${WORKFORCE_ONTOLOGY_VERSION}. Do not invent controlled concept IDs.`,
1878
+ `redacted must be true and ontologyVersion must be exactly ${WORKFORCE_ONTOLOGY_VERSION}; this pins graph semantics, not a finite vocabulary. Write all discovery-facing content in English and use faithful open namespaced concept IDs.`,
1861
1879
  ].join("\n");
1862
1880
  const selectionSchemaRequirements = [
1863
1881
  "Return the direct agentlas.workforce-selection.v1 JSON object. Do not emit schemaVersion=agentlas.workforce-leader-call.v1 and do not emit toolCall, name, or arguments wrappers. The host invokes workforce.validate_selection with your exact validated Selection.",
@@ -1887,15 +1905,14 @@ function buildPrompts(task, identity) {
1887
1905
  "Any specialized domain explicitly present in the task with distinct failure or accountability semantics must have its own accountable domain slot. Examples include payments, insurance, legal, finance, travel, and regulated science or operations. Never collapse such a named domain into generic backend, software, database, or implementation work. This is a general job-analysis rule, not a fixed list of required professions.",
1888
1906
  "forbiddenCommunities is not the inverse of selected communities and not an exhaustive list of unused professions. Add a global or slot exclusion only when the user explicitly prohibited that community or when participation is inherently incompatible with the assignment. Empty exclusion arrays are correct when no such negative constraint exists.",
1889
1907
  "Never forbid or exclude a broad ancestor, descendant, adjacent, or legitimately co-occurring community merely because a narrower job family was selected. Check every exclusion against all requiredCommunities and optionalCommunities before returning JSON.",
1890
- "Hard requirements mean absence makes the assignment impossible and the Hub catalog must prove eligibility; importance alone is not a hard gate. Prefer a broad required community plus optional skills when legacy declarations may be sparse. requiredRoles must default to []; there is no optionalRoles field, so express desired role fit through title, task, optionalCommunities, and optionalSkills unless the exact role declaration is truly execution-impossible to omit.",
1891
- "A requiredToolCapabilities entry means the selected worker itself must invoke that exact host tool. Designing a database, writing tests, or discussing a tool does not by itself require tool:database, tool:shell, or any other tool declaration.",
1892
- "consumes and produces require the selected Hub candidate profile itself to declare those exact artifacts. Ordinary workflow dependencies and handoffs belong in task and edges, not these hard fields.",
1908
+ "Hard requirements mean absence makes the assignment impossible and the Hub catalog must prove eligibility; importance alone is not a hard gate. Prefer a broad required community plus optional skills when legacy declarations may be sparse. Do not author requiredRoles; express desired role fit through title, task, optionalCommunities, and optionalSkills.",
1909
+ "Do not author requiredToolCapabilities, requiredAuthorities, forbiddenAuthorities, consumes, produces, or modalities: tools, authorities, and modalities attach to the executing runtime, not the agent card, and card-declaration gates on those fields only exclude real candidates. Ordinary workflow dependencies and handoffs belong in task and edges.",
1893
1910
  "Before returning JSON, self-check that every explicitly named specialized domain responsibility is independently represented, every primary domain responsibility has an accountable slot, every exclusion is explicit or inherently incompatible and does not conflict with job-family lineage, requiredRoles is empty unless strictly indispensable, and every other hard field passes the execution-impossible test.",
1894
1911
  "Return exactly one direct WorkOrder JSON object. Do not choose agents yet. Do not use ratings, popularity, invocation history, or revenue.",
1895
- "You must explicitly author every required schema field. The host will never fill or default a missing hard field.",
1912
+ "Explicitly author every structural field (slotId, title, task, cardinality, criticality, allowedEntityKinds, edges, forbiddenCommunities, selectionPolicy). A list-valued slot field you leave out is the empty constraint; the host normalizes absent to [].",
1896
1913
  "Never copy secrets, local file contents, account identifiers, or private memory into taskBrief; summarize them as local protected inputs and set redacted=true.",
1897
1914
  `ontologyVersion must be exactly ${WORKFORCE_ONTOLOGY_VERSION}.`,
1898
- WORKFORCE_ONTOLOGY_MENU,
1915
+ WORKFORCE_ONTOLOGY_GUIDE,
1899
1916
  searchSchemaRequirements,
1900
1917
  ].join("\n"),
1901
1918
  searchUser: task,
@@ -1906,12 +1923,12 @@ function buildPrompts(task, identity) {
1906
1923
  "Return a complete replacement WorkOrder authored by you. Preserve workOrderId and the redacted taskBrief exactly. Preserve every genuinely essential responsibility; add or separate an omitted accountable domain job family when the task requires it.",
1907
1924
  "Any specialized domain explicitly present in the task with distinct failure or accountability semantics must remain or become its own accountable domain slot. Examples include payments, insurance, legal, finance, travel, and regulated science or operations. Never collapse one into generic backend, software, database, or implementation work.",
1908
1925
  "Reconsider only hard eligibility gates exposed by the gap codes. gap:excluded:missing-required-skill means reassess requiredSkills and move desired expertise to optionalSkills/task unless exact profile proof is execution-essential. gap:excluded:missing-required-tool means remove or revise requiredToolCapabilities unless the worker itself must invoke that exact host tool. gap:excluded:missing-consumed-artifact and gap:excluded:missing-produced-artifact mean move normal workflow inputs/outputs to task or edges unless the candidate profile itself must declare that exact artifact. gap:excluded:entity-kind-mismatch means reconsider allowedEntityKinds and permit executable agent or team when it can own the accountability; never select group. gap:selection-requested-content-expansion means revisit the responsibility and semantic job-family description without reading candidate identities or content.",
1909
- "requiredRoles must default to []; because optionalRoles does not exist, move desired role fit to title, task, optionalCommunities, or optionalSkills unless absence of the exact declared role truly makes execution impossible. A required tool means the worker must invoke that exact host tool, not merely reason about the underlying system. consumes and produces are exact candidate-profile declaration gates; ordinary handoffs belong in task and edges.",
1926
+ "Do not author requiredRoles, requiredToolCapabilities, requiredAuthorities, forbiddenAuthorities, consumes, produces, or modalities; move desired fit to title, task, optionalCommunities, or optionalSkills ordinary handoffs belong in task and edges.",
1910
1927
  "Preserve community prohibitions explicitly stated in the redacted taskBrief. You may correct exclusions inferred by the prior job analysis when they conflict with required/optional job-family lineage or when coverage gap codes show forbidden-community exclusion. Never turn forbiddenCommunities or excludedCommunities into an exhaustive list of unused families, and never forbid a broad, adjacent, or legitimately co-occurring community merely to sharpen a slot.",
1911
1928
  "Before returning JSON, self-check that each explicitly named specialized domain responsibility has an independent accountable slot and that every hard gate still satisfies the execution-impossible or exact-profile-declaration test.",
1912
1929
  "The host will validate your replacement exactly and will not add slots, defaults, constraints, candidates, or substitutions. At most two total semantic WorkOrder refinements are allowed.",
1913
1930
  `ontologyVersion must remain exactly ${WORKFORCE_ONTOLOGY_VERSION}.`,
1914
- WORKFORCE_ONTOLOGY_MENU,
1931
+ WORKFORCE_ONTOLOGY_GUIDE,
1915
1932
  searchSchemaRequirements,
1916
1933
  ].join("\n"),
1917
1934
  selectionSystem: [
@@ -10,12 +10,161 @@
10
10
  * 명시적 --visibility marketplace 로만 publish가 된다.
11
11
  * 오류는 throw → 명령 파일이 ctx.err + return 1 로 처리한다 (process.exit 금지).
12
12
  */
13
+ const fs = require("node:fs");
14
+ const os = require("node:os");
15
+ const path = require("node:path");
16
+ const readline = require("node:readline/promises");
13
17
  const cloudRuntime = require("../agentlas-cloud-runtime.cjs");
14
18
  const { installHubAgent } = require("../hub/install.cjs");
15
19
  const { callHubTool, HubError } = require("../cloud/hub-client.cjs");
16
20
  const { packageCloudAgent } = require("./package.cjs");
17
21
  const { deleteCloudAgent } = require("./cas.cjs");
18
22
  const { listOwnedCloudAgents, restoreOwnedCloudAgent } = require("./restore.cjs");
23
+ const workforceDeps = require("../workforce/deps.cjs");
24
+ const workforceCapture = require("../workforce/capture.cjs");
25
+
26
+ const PURPOSE_QUESTION = "What concrete work should this agent complete, and what should the finished result look like?";
27
+
28
+ function purposeRepairNeeded(result) {
29
+ const ids = new Set((result?.review?.findings || []).map((finding) => finding.id));
30
+ return ids.has("routing-card-required") ||
31
+ (result?.review?.findings || []).some((finding) =>
32
+ finding.id === "routing-card-invalid" && /summary|capabilities|name|id|workforce/i.test(finding.message || ""));
33
+ }
34
+
35
+ async function askPurpose(ctx, flags) {
36
+ if (typeof flags.purpose === "string" && flags.purpose.trim()) return flags.purpose.trim();
37
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return "";
38
+ const prompt = ctx.lang === "ko"
39
+ ? "이 에이전트가 실제로 끝내야 할 일과, 완료됐을 때 나와야 할 결과를 평소 말하듯 적어주세요: "
40
+ : `${PURPOSE_QUESTION} `;
41
+ const terminal = readline.createInterface({ input: process.stdin, output: process.stdout });
42
+ try {
43
+ return String(await terminal.question(prompt)).trim();
44
+ } finally {
45
+ terminal.close();
46
+ }
47
+ }
48
+
49
+ function parsePurposeProjection(raw) {
50
+ const text = String(raw || "").trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "");
51
+ const value = JSON.parse(text);
52
+ const ids = (input, prefix, min = 1) => {
53
+ if (!Array.isArray(input)) throw new Error(`${prefix} must be an array`);
54
+ const pattern = prefix === "capability"
55
+ ? /^[a-z][a-z0-9]*(_[a-z0-9]+)+$/
56
+ : new RegExp(`^${prefix}:[a-z0-9][a-z0-9-]*$`);
57
+ const output = [...new Set(input.map((item) => String(item).trim()).filter((item) => pattern.test(item)))];
58
+ if (output.length < min) throw new Error(`${prefix} is incomplete`);
59
+ return output;
60
+ };
61
+ const requiredText = (key) => {
62
+ const output = typeof value[key] === "string" ? value[key].trim() : "";
63
+ if (!output) throw new Error(`${key} is missing`);
64
+ return output;
65
+ };
66
+ return {
67
+ titleEn: requiredText("titleEn"),
68
+ titleKo: requiredText("titleKo"),
69
+ summary: requiredText("summary"),
70
+ descriptionKo: requiredText("descriptionKo"),
71
+ capabilities: ids(value.capabilities, "capability", 2),
72
+ roles: ids(value.roles, "role"),
73
+ communities: ids(value.communities, "community"),
74
+ skills: ids(value.skills, "skill", 2),
75
+ knowledge: ids(value.knowledge, "knowledge"),
76
+ };
77
+ }
78
+
79
+ async function projectPurposeWithConnectedModel(ctx, answer, fallbackName) {
80
+ const runtime = workforceDeps.resolveWorkforceRuntime(ctx.db()).roleRuntimes.orchestrator;
81
+ const system = [
82
+ "Convert an ordinary-language agent purpose into one English internal routing resume.",
83
+ "The answer may be in any language. Preserve meaning; do not add tools, runtimes, languages, modalities, permissions, authorities, or forbidden authorities.",
84
+ "Return JSON only.",
85
+ ].join(" ");
86
+ const prompt = [
87
+ `Working name: ${fallbackName}`,
88
+ `User explanation: ${answer}`,
89
+ "",
90
+ 'Return {"titleEn":"","titleKo":"","summary":"","descriptionKo":"","capabilities":[],"roles":[],"communities":[],"skills":[],"knowledge":[]}.',
91
+ "summary must be a concrete English description of work and finished result.",
92
+ "capabilities are 2-8 English snake_case verb-object phrases.",
93
+ "roles, communities, skills, knowledge are faithful open-world English IDs with role:, community:, skill:, knowledge: prefixes.",
94
+ "titleKo and descriptionKo are display translations only; every routing field remains English.",
95
+ ].join("\n");
96
+ let raw;
97
+ if (runtime.mode === "api") {
98
+ raw = await workforceCapture.runApi(runtime.backend, runtime.model, system, prompt, {});
99
+ } else {
100
+ if (runtime.kind === "gemini") {
101
+ throw new Error("The connected Gemini CLI cannot run this no-tool repair safely yet. Choose Codex/Claude or a BYOK model and retry.");
102
+ }
103
+ const env = await workforceCapture.buildChildEnv(ctx.db(), { cwd: process.cwd(), projectPath: process.cwd() });
104
+ raw = await workforceCapture.captureRuntime(runtime.kind, system, prompt, {
105
+ cwd: workforceCapture.runCwd(),
106
+ env,
107
+ permission: "read",
108
+ authorityMode: "no-authority",
109
+ model: runtime.model,
110
+ effort: runtime.effort,
111
+ timeoutConfig: { connectMs: 30_000, idleMs: 45_000, totalMs: 90_000 },
112
+ outputLimitBytes: 256 * 1024,
113
+ });
114
+ }
115
+ return parsePurposeProjection(raw);
116
+ }
117
+
118
+ function writePurposeRepairCopy(root, projection) {
119
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "agentlas-purpose-repair-"));
120
+ fs.cpSync(path.resolve(root), tempRoot, { recursive: true, dereference: false });
121
+ const metadataDir = path.join(tempRoot, ".agentlas");
122
+ fs.mkdirSync(metadataDir, { recursive: true });
123
+ const routingPath = path.join(metadataDir, "routing-card.json");
124
+ let routing = {};
125
+ try { routing = JSON.parse(fs.readFileSync(routingPath, "utf8")); } catch { /* construct it */ }
126
+ const id = String(routing.id || path.basename(root))
127
+ .normalize("NFKC").toLowerCase().replace(/[^a-z0-9._/-]+/g, "-").replace(/^-|-$/g, "") || "agent";
128
+ const card = {
129
+ ...routing,
130
+ schemaVersion: "routing-card/2.0",
131
+ id,
132
+ type: routing.type === "team" || routing.type === "plugin" ? routing.type : "agent",
133
+ name: projection.titleEn,
134
+ summary: projection.summary.slice(0, 240),
135
+ capabilities: projection.capabilities,
136
+ routing_status: "routing_ready",
137
+ workforce: {
138
+ ...(routing.workforce && typeof routing.workforce === "object" && !Array.isArray(routing.workforce)
139
+ ? routing.workforce
140
+ : {}),
141
+ roles: projection.roles,
142
+ communities: projection.communities,
143
+ skills: projection.skills,
144
+ knowledge: projection.knowledge,
145
+ languages: [],
146
+ modalities: [],
147
+ },
148
+ };
149
+ fs.writeFileSync(routingPath, `${JSON.stringify(card, null, 2)}\n`, "utf8");
150
+ const agentCardPath = path.join(metadataDir, "agent-card.json");
151
+ let agentCard = {};
152
+ try { agentCard = JSON.parse(fs.readFileSync(agentCardPath, "utf8")); } catch { /* construct it */ }
153
+ agentCard = {
154
+ ...agentCard,
155
+ name: agentCard.name || projection.titleEn,
156
+ summary: projection.summary,
157
+ localized: {
158
+ ...(agentCard.localized && typeof agentCard.localized === "object" ? agentCard.localized : {}),
159
+ titleEn: projection.titleEn,
160
+ titleKo: projection.titleKo,
161
+ descriptionEn: projection.summary,
162
+ descriptionKo: projection.descriptionKo,
163
+ },
164
+ };
165
+ fs.writeFileSync(agentCardPath, `${JSON.stringify(agentCard, null, 2)}\n`, "utf8");
166
+ return tempRoot;
167
+ }
19
168
 
20
169
  function parseCloudFlags(args) {
21
170
  const flags = { _: [] };
@@ -102,6 +251,7 @@ const CLOUD_HELP = [
102
251
  " save owner-private in Agent Cloud (default upload)",
103
252
  " publish <path> [--dry-run] [--slug name]",
104
253
  " explicitly publish to the public Agentlas Hub",
254
+ " --purpose \"ordinary explanation\" repair a missing purpose through your connected model",
105
255
  " package <path> [--json] [--visibility private-link|marketplace]",
106
256
  " --overwrite 서버에 더 새 버전이 있어도 지금 폴더 내용으로 덮어쓰기",
107
257
  " package only; defaults to private-save checks",
@@ -238,13 +388,30 @@ async function runCloud(ctx, args) {
238
388
  if (!root) { ctx.err(`usage: agentlas cloud ${sub} <path>`); return 1; }
239
389
  const visibility = cloudVisibilityForAction(sub, flags);
240
390
  const dryRun = sub === "package" || Boolean(flags["dry-run"]);
241
- const result = await packageCloudAgent(ctx.db(), root, {
391
+ const packageOptions = {
242
392
  slug: typeof flags.slug === "string" ? flags.slug : undefined,
243
393
  visibility,
244
394
  llmReview: Boolean(flags["llm-review"]),
245
395
  overwriteRemote: Boolean(flags.overwrite),
246
396
  dryRun,
247
- });
397
+ };
398
+ let result = await packageCloudAgent(ctx.db(), root, packageOptions);
399
+ if (sub === "publish" && result.status === "blocked" && purposeRepairNeeded(result) && !flags.json) {
400
+ const answer = await askPurpose(ctx, flags);
401
+ if (answer) {
402
+ const projection = await projectPurposeWithConnectedModel(ctx, answer, path.basename(path.resolve(root)));
403
+ const repairedRoot = writePurposeRepairCopy(root, projection);
404
+ try {
405
+ result = await packageCloudAgent(ctx.db(), repairedRoot, packageOptions);
406
+ } finally {
407
+ fs.rmSync(repairedRoot, { recursive: true, force: true });
408
+ }
409
+ } else {
410
+ ctx.err(ctx.lang === "ko"
411
+ ? `업로드를 완성하려면 답이 필요합니다. 다시 실행할 때 --purpose "평소 말로 설명"을 붙이세요.`
412
+ : `This upload needs one answer. Retry with --purpose "your ordinary explanation".`);
413
+ }
414
+ }
248
415
  if (flags.json) {
249
416
  ctx.out(JSON.stringify(result, null, 2));
250
417
  return (sub === "save" || sub === "publish") && result.status === "blocked" ? 1 : 0;
@@ -269,6 +436,8 @@ module.exports = {
269
436
  cloudVisibilityFlag,
270
437
  cloudVisibilityForAction,
271
438
  cloudActionForTopLevelUpload,
439
+ purposeRepairNeeded,
440
+ parsePurposeProjection,
272
441
  printCloudPackageResult,
273
442
  runCloud,
274
443
  runUpload,
@@ -577,6 +577,7 @@ function cloudRoutingCardProblem(card) {
577
577
  if (card.type !== "agent" && card.type !== "team" && card.type !== "plugin") return "type must be agent, team, or plugin";
578
578
  if (typeof card.name !== "string" || !card.name.trim()) return "name must be a non-empty string";
579
579
  if (typeof card.summary !== "string" || !card.summary.trim()) return "summary must be a non-empty string";
580
+ if (card.summary.length > 240) return "summary must be at most 240 characters";
580
581
  if (!Array.isArray(card.capabilities) || card.capabilities.length === 0) return "capabilities must be a non-empty array";
581
582
  for (const capability of card.capabilities) {
582
583
  if (typeof capability !== "string" || !CLOUD_ROUTING_CARD_CAPABILITY_RE.test(capability)) {
@@ -586,6 +587,33 @@ function cloudRoutingCardProblem(card) {
586
587
  if (typeof card.routing_status !== "string" || !CLOUD_ROUTING_CARD_STATUSES.has(card.routing_status)) {
587
588
  return "routing_status must be draft, searchable, candidate, routing_ready, or trusted";
588
589
  }
590
+ const workforce = card.workforce;
591
+ if (!workforce || typeof workforce !== "object" || Array.isArray(workforce)) {
592
+ return "workforce must be a complete semantic resume";
593
+ }
594
+ const semanticLists = [
595
+ ["communities", /^community:[a-z0-9][a-z0-9-]*$/, 1, 5],
596
+ ["roles", /^role:[a-z0-9][a-z0-9-]*$/, 0, 4],
597
+ ["skills", /^skill:[a-z0-9][a-z0-9-]*$/, 1, 12],
598
+ ["knowledge", /^knowledge:[a-z0-9][a-z0-9-]*$/, 0, 256],
599
+ ];
600
+ for (const [field, pattern, minimum, maximum] of semanticLists) {
601
+ const values = workforce[field];
602
+ if (!Array.isArray(values) || values.length < minimum || values.length > maximum) {
603
+ return `workforce.${field} must contain ${minimum}-${maximum} English semantic IDs`;
604
+ }
605
+ if (new Set(values).size !== values.length ||
606
+ values.some((value) => typeof value !== "string" || !pattern.test(value))) {
607
+ return `workforce.${field} contains an invalid or duplicate semantic ID`;
608
+ }
609
+ }
610
+ for (const field of ["languages", "modalities"]) {
611
+ const values = workforce[field];
612
+ if (!Array.isArray(values) || new Set(values).size !== values.length ||
613
+ values.some((value) => typeof value !== "string")) {
614
+ return `workforce.${field} must be a unique string array`;
615
+ }
616
+ }
589
617
  return null;
590
618
  }
591
619
 
@@ -12,6 +12,7 @@ const {
12
12
  installPluginMcpRows,
13
13
  listHubPlugins,
14
14
  } = require("../hub/plugins.cjs");
15
+ const { terminalProjectCandidateCli, initializedAgentlasProjectPathCli } = require("../project/state.cjs");
15
16
 
16
17
  async function pluginAdd(ctx, slug) {
17
18
  if (!slug) {
@@ -90,12 +91,90 @@ async function pluginList(ctx) {
90
91
  return 0;
91
92
  }
92
93
 
94
+ async function pluginListWithProject(ctx, projectPath) {
95
+ const root = terminalProjectCandidateCli(projectPath);
96
+ if (!root) {
97
+ ctx.err(`project path is invalid or not safe: ${projectPath}`);
98
+ return 1;
99
+ }
100
+ if (!initializedAgentlasProjectPathCli(root)) {
101
+ ctx.err("project is not initialized for plugin listing. Run `agentlas project init` in that folder first.");
102
+ return 1;
103
+ }
104
+ ctx.out("Scope: global Hub plugin catalog (project compatibility and local installation state are not evaluated).");
105
+ return pluginList(ctx);
106
+ }
107
+
108
+ function parsePluginListFlags(args) {
109
+ const flags = {
110
+ global: false,
111
+ project: null,
112
+ errors: [],
113
+ positionals: [],
114
+ };
115
+ for (let i = 0; i < args.length; i++) {
116
+ const arg = args[i];
117
+ if (arg === "--global") {
118
+ flags.global = true;
119
+ continue;
120
+ }
121
+ if (arg === "--project") {
122
+ const next = args[i + 1];
123
+ if (!next || next.startsWith("-")) {
124
+ flags.errors.push("usage: agentlas plugin list --project <path>");
125
+ return flags;
126
+ }
127
+ flags.project = next;
128
+ i += 1;
129
+ continue;
130
+ }
131
+ if (arg === "--help" || arg === "-h") {
132
+ flags.errors.push("usage: agentlas plugin list [--global | --project <path>]");
133
+ return flags;
134
+ }
135
+ if (arg.startsWith("--")) {
136
+ flags.errors.push(`unknown flag: ${arg}`);
137
+ return flags;
138
+ }
139
+ flags.positionals.push(arg);
140
+ }
141
+ if (flags.project && flags.global) {
142
+ flags.errors.push("cannot use --project and --global together");
143
+ }
144
+ if (flags.positionals.length > 0) {
145
+ flags.errors.push("usage: agentlas plugin list [--global | --project <path>]");
146
+ }
147
+ return flags;
148
+ }
149
+
150
+ async function runPluginList(ctx, args) {
151
+ const flags = parsePluginListFlags(args);
152
+ if (flags.errors.length) {
153
+ ctx.err(flags.errors[0]);
154
+ return 1;
155
+ }
156
+ if (flags.project) return pluginListWithProject(ctx, flags.project);
157
+ return pluginList(ctx);
158
+ }
159
+
93
160
  function run(ctx, args) {
94
161
  const action = args[0];
95
- if (action === "add") return pluginAdd(ctx, args[1]);
96
- if (action === "list" || !action) return pluginList(ctx);
162
+ if (action === "add") {
163
+ if (args.length !== 2) {
164
+ ctx.err("usage: agentlas plugin add <slug> (run agentlas plugin list first)");
165
+ return 1;
166
+ }
167
+ return pluginAdd(ctx, args[1]);
168
+ }
169
+ if (action === "list" || !action) {
170
+ const rest = action === "list" ? args.slice(1) : [];
171
+ return runPluginList(ctx, rest);
172
+ }
97
173
  ctx.err("usage: agentlas plugin add <slug> | agentlas plugin list");
98
174
  return 1;
99
175
  }
100
176
 
101
- module.exports = { run };
177
+ module.exports = {
178
+ run,
179
+ parsePluginListFlags,
180
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlas",
3
- "version": "1.0.15",
3
+ "version": "1.0.17",
4
4
  "description": "Agentlas agent terminal — chat with your installed AI agents and teams from the terminal, Claude Code style. Standalone: no desktop app required.",
5
5
  "bin": {
6
6
  "agentlas": "bin/agentlas.cjs"