agentlas 0.5.5 → 0.7.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/README.md CHANGED
@@ -87,6 +87,77 @@ agentlas import <폴더> # 로컬 에이전트/팀 임포트
87
87
  agentlas list # 설치된 에이전트/회사 + 활성 런타임
88
88
  ```
89
89
 
90
+ **Portable Experience Bundle과 Variant**
91
+ ```sh
92
+ agentlas experience validate <bundle.agentlas-experience.json>
93
+ agentlas experience save <bundle> --base-cloud-id <id> --base-package-hash sha256:<hash>
94
+ agentlas experience publish <bundle> --visibility unlisted \
95
+ --base-cloud-id <id> --base-package-hash sha256:<hash>
96
+ agentlas experience status <bundle-id|upload-id>
97
+ agentlas experience export <bundle-id|upload-id> [--out <file>] [--overwrite]
98
+ agentlas experience withdraw <bundle-id|upload-id>
99
+
100
+ # 로컬 0600 캐시만 만들고 Hub에는 보내지 않음
101
+ agentlas experience save <bundle> --local-only
102
+
103
+ # 이전 pack-only 로컬 의도 호환 명령
104
+ agentlas experience legacy-publish .agentlas/experience-pack.json
105
+ agentlas experience unpublish <pack-id|release-id>
106
+
107
+ agentlas variant resolve --candidates variants.json --base-release <release-id>
108
+ ```
109
+
110
+ `validate`는 256 items/3 MiB 한도, NFC canonical hash, 비밀값·PII·raw prompt/transcript·
111
+ 로컬 경로·base package·MCP 실행 정의 유입을 모델 호출 없이 검사한다. `save`는 정확한
112
+ Cloud base artifact를 서버에서 확인한 뒤 owner-private `draft-saved`로 저장한다.
113
+ `publish`도 곧바로 공개하지 않고 `verification-requested`까지만 요청한다. evaluator가
114
+ 검증한 새 release와 별도 Variant가 없으면 공개 활성·평판·자동대여 권위가 생기지 않는다.
115
+ 모든 변경 요청은 기존 로그인 세션, Idempotency-Key, exact revision ETag를 사용하고,
116
+ Terminal은 서버 영수증과 별도의 0600 로컬 상태만 보관한다. `--dry-run`은 로그인 확인,
117
+ 네트워크, 로컬 저장을 모두 하지 않는다.
118
+ `export`는 서버가 돌려준 bundle hash/semantic content/영수증 owner를 다시 검증한 뒤
119
+ 0600 파일로 원자적으로 저장한다. 기본은 기존 출력 파일을 덮어쓰지 않고 symlink는
120
+ 항상 거절하며, 같은 일반 파일을 명시적으로 교체할 때만 `--overwrite`를 사용한다.
121
+
122
+ 이전 pack-only `publish|unpublish` 동작은 `legacy-publish|unpublish` 로컬 의도로만 남는다.
123
+ full Portable Bundle을 `publish`하면 새 서버 교환 경로를 사용한다. Experience Bundle은
124
+ base release를 참조할 뿐 base package를 복사하지 않는다.
125
+
126
+ `variant resolve`도 로컬 호환성 미리보기다. 후보 JSON의 `score`나
127
+ `compatibilityStatus: verified`는 사용자가 직접 쓸 수 있으므로 평판·결제·대여·실행
128
+ 권위로 인정하지 않는다. 실제 자동대여에는 Agentlas Web이 발급·검증한 서버 resolution
129
+ receipt가 별도로 필요하다.
130
+
131
+ **빌드 전 MCP 계획**
132
+ ```sh
133
+ agentlas build "GitHub 이슈를 정리하는 에이전트" --mcp-plan-only
134
+ agentlas build "GitHub 이슈를 정리하는 에이전트" --approve-mcp github
135
+ agentlas build "오프라인 에이전트" --no-mcp
136
+ agentlas build "Windows 셸 에이전트" \
137
+ --experience-base-release <exact-release-id> \
138
+ --experience-task-signature task:windows-shell \
139
+ --experience-environment windows-x64
140
+ agentlas run <agent> "Windows 작업" \
141
+ --experience-base-release <exact-release-id> \
142
+ --experience-task-signature task:windows-shell \
143
+ --experience-environment windows-x64
144
+ ```
145
+
146
+ 빌드 전에는 Agentlas 시스템 전역 MCP 레지스트리의 메타데이터를 먼저 읽고, 관련
147
+ MCP·키 필요 여부·키 존재 여부만 한 번에 보여 준다. 명령, 인자, URL, 키 값은 빌더에
148
+ 전달하지 않는다. 비대화형 실행은 묻지 않고 기본적으로 아무 MCP도 승인하지 않아
149
+ CI가 멈추지 않는다. 한 MCP가 없거나 키가 없어도 그 기능만 degraded가 되며 빌드는
150
+ empty-MCP 모드로 계속된다. `--require-mcp <catalog-id>`는 빌드 전체를 중단시키지 않고
151
+ Variant 대여 판단에서 해당 Variant만 제외하는 계약을 만든다.
152
+ 시스템 전역 레지스트리를 읽지 못한 경우도 `registry: unavailable`로 명확히 구분하고,
153
+ 설치나 네트워크 폴백 없이 empty-MCP 모드로 계속한다.
154
+ REPL의 `/build`도 top-level `agentlas build`와 같은 계획·한 번 동의·결과 경로를
155
+ 사용하며, MCP 사전 계획을 건너뛰는 별도 builder 지름길은 없다.
156
+ 로컬 promoted Experience도 같은 handler에서 exact base release, 현재 프로젝트,
157
+ 명시 task signature, environment constraint가 모두 맞을 때만 붙는다. 합계 8개/800
158
+ 추정 토큰을 넘지 않으며, prompt에는 `NO SERVER RENTAL-RESOLUTION RECEIPT`가 표시되어
159
+ 로컬 사용자 증언을 서버 검증 평판처럼 오인하지 않게 한다.
160
+
90
161
  **내 Agent Cloud 자산**
91
162
  ```sh
92
163
  agentlas cloud save <경로> # 소유자 전용 비공개 저장(공개 심사/라우팅 카드 없음)
@@ -97,6 +168,8 @@ agentlas cloud restore <slug> # 전체 hash 검증 후 이 컴퓨터에 exa
97
168
 
98
169
  비공개 저장도 업로드 전 로컬에서 비밀값, 안전하지 않은 경로, 파일별 hash와
99
170
  전체 package hash를 검사한다. 공개 Hub 발행에만 라우팅 카드와 공개 검토가 붙는다.
171
+ `.agentlas/experience-relations.jsonl`과 `.previous`/hidden temp siblings는 로컬에서
172
+ 재생성되는 Experience 관계 인덱스이므로 base Agent hash와 Cloud bundle에서 제외된다.
100
173
 
101
174
  `agentlas cloud install <slug>`은 기존 호환 명령이며 공개 Hub 설치다. 비공개
102
175
  Agent Cloud 소유자 복원은 반드시 `agentlas cloud restore <slug>`를 사용한다.
@@ -41,19 +41,50 @@ function runtimeFromSpec(spec) {
41
41
  }
42
42
 
43
43
  // Does this agent's job involve generating/handling images?
44
+ // /그림(?!자)/ — "그림자"(shadow)는 이미지 힌트가 아니다.
44
45
  const IMAGE_HINTS = [
45
- /image/i, /이미지/, /그림/, /\bdesign\b/i, /디자인/, /쇼핑몰/, /상품\s*(사진|이미지|상세)/, /상세\s*페이지/,
46
+ /image/i, /이미지/, /그림(?!자)/, /\bdesign\b/i, /디자인/, /쇼핑몰/, /상품\s*(사진|이미지|상세)/, /상세\s*페이지/,
46
47
  /thumbnail/i, /썸네일/, /banner/i, /배너/, /poster/i, /포스터/, /visual/i, /비주얼/, /illustrat/i, /일러스트/,
47
48
  /로고/, /\blogo\b/i, /사진/, /photo/i, /nano-?banana/i, /imagen/i, /이미지\s*생성/, /그래픽/, /graphic/i,
48
49
  ];
49
50
  // 빌더/메타/조율/거버넌스 역할은 (이미지 에이전트를 *만들* 수는 있어도) 스스로 이미지를 생산하지 않는다.
50
51
  // 이런 역할이 system_prompt에 "이미지/디자인"을 언급한다는 이유로 gemini로 끌려가면 코드/빌드 품질이 떨어진다.
51
52
  const NON_IMAGE_ROLES = new Set(["meta", "builder", "orchestrator", "pm", "curator", "governance"]);
53
+ // 부정/거절 문장("이미지 생성 금지", "영상·이미지 생성은 하지 않는다")은 능력이 아니라
54
+ // 반(反)능력 선언이다 — 그 문장 안의 힌트는 세지 않는다. 단, "묻지 않고 바로 생성한다"처럼
55
+ // 긍정문에 흔한 보조 부정("않고","없다" 단독)은 잡지 않도록 강한 금지 구문만 매칭한다.
56
+ const IMAGE_NEGATION_RE = /(금지|하지\s*않|하지\s*마|말\s*것|거절|불가(?!피)|아니다|refuse|\bnever\b|\bdo(es)?\s+not\b|\bdon'?t\b)/i;
57
+ // 이미지 생성 도구 이름은 단독으로도 이미지 생산 역할의 강한 증거다.
58
+ const IMAGE_TOOL_MARKERS = [/nano-?banana/i, /\bimagen\b/i, /gpt-image/i, /grok\s*imagine/i];
59
+ // 사고(2026-07-12): appbridge CEO 프롬프트의 "코드/디자인/스토어 결정의 owner가 아니다" 속
60
+ // "디자인" 한 단어로 이미지 에이전트 판정 → PPT 요청 세션이 통째로 gemini로 전환됐다.
61
+ // 수리: 정체성 존(이름/태그라인)은 그대로 신뢰하되, 본문 단독으로는 "힌트를 포함한 긍정문
62
+ // 3문장 이상"을 요구한다. 문장 단위로 세므로 "상품 이미지 생성 금지" 같은 한 문장이
63
+ // 겹치는 정규식 여러 개를 동시에 때려도 1클러스터다.
64
+ const MIN_BODY_IMAGE_SENTENCES = 3;
65
+ const BODY_SCAN_CAP = 16000; // 로컬 임포트 상한과 동일 — 클라우드 무제한 프롬프트의 전문 스캔 방지
52
66
  function needsImage(agent) {
53
67
  if (!agent) return false;
54
68
  if (NON_IMAGE_ROLES.has(String(agent.role || "").toLowerCase())) return false;
55
- const hay = `${agent.name || ""} ${agent.name_en || ""} ${agent.tagline || ""} ${agent.tagline_en || ""} ${agent.system_prompt || ""}`;
56
- return IMAGE_HINTS.some((re) => re.test(hay));
69
+ // 정체성 존은 사용자가 선언한 이름/태그라인만 slug는 폴더명에서 기계 파생되므로
70
+ // ("design-system" 리포 임포트 등) 단독 신뢰 대상이 아니다.
71
+ const identity = [agent.name, agent.name_en, agent.tagline, agent.tagline_en].filter(Boolean).join(" ");
72
+ if (IMAGE_HINTS.some((re) => re.test(identity))) return true;
73
+ // 팀 CEO 두뇌(부서 소개·위임 규칙)에서는 body 키워드가 역할 증거가 아니다 — vibecoder처럼
74
+ // "Design and Publishing HQ" 부서명이 10문장씩 나오는 조율용 프롬프트가 이미지 팀으로
75
+ // 오판되던 사례. entity_kind='team'은 사용자가 선언한 정체성 존만 신뢰한다.
76
+ if (String(agent.entity_kind || "").toLowerCase() === "team") return false;
77
+ const body = String(agent.system_prompt || "").slice(0, BODY_SCAN_CAP);
78
+ let clusters = 0;
79
+ for (const sentence of body.split(/[\n.!?。!?]+/)) {
80
+ if (!sentence || IMAGE_NEGATION_RE.test(sentence)) continue;
81
+ if (IMAGE_TOOL_MARKERS.some((re) => re.test(sentence))) return true;
82
+ if (IMAGE_HINTS.some((re) => re.test(sentence))) {
83
+ clusters += 1;
84
+ if (clusters >= MIN_BODY_IMAGE_SENTENCES) return true;
85
+ }
86
+ }
87
+ return false;
57
88
  }
58
89
 
59
90
  // Auto-pick a runtime spec for an agent given installed CLI kinds and the session default spec.
@@ -110,6 +110,9 @@ function buildComposerFrame(state, ctx = {}, palette, width = 80) {
110
110
  const rest = ctx.status && available > 0 ? c.faint(" · " + truncateWidth(ctx.status, available)) : "";
111
111
  lines.push(permissionPresentation(ctx, c) + rest);
112
112
  }
113
+ // 연결 LLM 세션 사용량 상시 표시줄 — 입력박스 바로 아래에 항상 유지된다.
114
+ const usageText = typeof ctx.usage === "function" ? ctx.usage() : ctx.usage;
115
+ if (usageText) lines.push(c.faint(truncateWidth(String(usageText), w)));
113
116
  if (ctx.confirmation) {
114
117
  const prefix = ctx.confirmationTone === "danger" ? "! " : "✓ ";
115
118
  const paint = ctx.confirmationTone === "danger" && c.paw ? c.paw : c.green || c.emerald || ((value) => value);