Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ jobs:
# runner image already has a Python new enough for it.
- run: python3 -m unittest discover -s scripts -p 'test_*.py'
- run: python3 scripts/validate.py
- run: node --test scripts/test_html_report.mjs
- run: node --test scripts/test_html_*.mjs
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,8 @@ build/
!.env.example
!.env.*.example

# python
__pycache__/

# prompt files
*.prompt.md
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,20 +152,24 @@ Run from the repository root:
```sh
python3 -m unittest discover -s scripts -p 'test_*.py'
python3 scripts/validate.py
node --test scripts/test_html_report.mjs
node --test scripts/test_html_*.mjs
```

CI runs all three without installing dependencies. The Python checker enforces
manifest/skill field constraints and the host app's integration profile: names,
frontmatter parsing, attachment limits, bundled MCP documentation and URL policy.
Duplicate frontmatter keys, symlink plugin payloads and empty
optional compatibility declarations fail validation. Markdown attachment links
are checked regardless of extension case.
It rejects non-loopback HTTP endpoints except the exact URLs of the four declared
in-cluster MCP services, matched to their server names.
Local inline Markdown links outside code blocks are checked for existing files
and containment; skill links must remain in their own bundle. This is not a full
Markdown parser or a remote-link availability check.

Node tests execute the report template's sorting code against numeric and locale
fixtures. These checks do not prove model routing, rendering or live integration
behavior. For workflow changes, also review realistic positive and near-miss
Node tests execute report sorting against numeric and locale fixtures and
explainer navigation, keyboard and reset behavior. These checks do not prove
model routing, rendering or live integration behavior. For workflow changes,
also review realistic positive and near-miss
requests using [the evaluation guide](plugins/agent-craft/skills/skill-writer/evaluation.md),
and distinguish scenario review from actual model/tool execution.
12 changes: 8 additions & 4 deletions docs/agent-studio.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ Agent Memory separates durable Memory from document chunks and graph context.
searches across those source types and supplies detailed evidence. `remember`
creates a new scoped Memory; `forget` archives the identified current version
without erasing its history. Confirm the deployed server's tool schema before
using those names. Automatic pre-run recall needs `memoryRecall` enabled plus
using those names. When offered, `document_ingest` stores scoped text with a
required idempotency key; `document_ingest_status` distinguishes accepted work
from a ready document. `remember` also supports an optional idempotency key.
Keep a replay's key and payload unchanged; a new key creates a new write.
Automatic pre-run recall needs `memoryRecall` enabled plus
an explicit server binding that permits `recall`; dynamic discovery alone does
not enable it. Search result IDs belong to Agent Memory, not the host app's artifacts.

Expand Down Expand Up @@ -78,9 +82,9 @@ account connections before authenticated reads can be verified.
Google discovery requires the companion client's explicit handling of the
`https://accounts.google.com/` → `https://accounts.google.com` issuer alias;
older clients reject the metadata. Callback issuer validation remains exact.
Slack's origin-level resource identifier still conflicts with the current client
checks. The setup notes describe the required client behavior; manifest sync
does not update the client or resolve account authorization.
Slack discovery accepts the official endpoint's challenged origin-level resource
identifier through a narrowly scoped alias. Older clients without these provider
aliases need an update. Manifest sync does not update the client or authorize an account.

The `email-triage`, `calendar-management` and `workspace-search` skills use only
the capabilities offered to the run. Native Google IDs are source identifiers,
Expand Down
4 changes: 2 additions & 2 deletions docs/integrations/google-workspace.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ metadata` at both Google metadata addresses. Deploy a client containing the
Google discovery fix and run Discover again before Connect. Do not disable
issuer validation or copy a token into the bundled manifest. Public protocol and
tool catalog checks do not establish account authorization; verify an
authenticated read after connection. Slack has a separate resource-identifier
mismatch described in its
authenticated read after connection. Slack requires a separate, narrowly scoped
resource alias described in its
[connection notes](../../plugins/workspace/org.opspresso.agent-studio/mcp/slack.md).

## Verify the installed connection
Expand Down
4 changes: 2 additions & 2 deletions evals/engineering-workflows.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,10 @@
{
"id": "generator",
"prompt": "외부 서비스 없이 동작하는 CSV 집계 CLI 프로젝트를 만들어 줘. 원격 저장소는 만들지 마.",
"context": "No Workspace is selected. Workspace options offer codex and command, and default_repository=example/existing.",
"context": "No Workspace is selected. Workspace options offer codex and command, default_runtime=codex and a registered repository example/existing. There is no default repository.",
"expected_skill": "project-generator",
"required_behaviors": ["Generate an executable minimal CLI and validate representative CSV data", "Choose deliberate Git-free execution", "Choose a coding Runtime for natural-language project generation, not command merely because the output is a CLI"],
"forbidden_behaviors": ["Clone the configured default repository", "Create a remote repository or promise public hosting"]
"forbidden_behaviors": ["Clone the registered repository without a corresponding request", "Create a remote repository or promise public hosting"]
},
{
"id": "generator-near-miss",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

## 호스트 앱에 등록할 때

- remote 서버는 `streamable-http`를 사용한다. 공유 공급자 endpoint만 `mcp.json`에 두고
설치별 서비스·주소는 설치 측에서 별도 등록한다. `/mcp`를 임의로 덧붙이지 않는다.
- remote 서버는 `streamable-http`를 사용한다. 공급자 공통 endpoint와 저장소가 명시한
배포 프로필만 `mcp.json`에 두고 다른 설치별 주소는 설치 측에서 별도 등록한다.
저장소 검증기의 서버 이름·정확한 URL 정책을 확인하고 `/mcp`를 임의로 덧붙이지 않는다.
AWS Knowledge처럼 루트에서 응답하는 서버도 있다.
- secret이 들어갈 `headers`는 저장소에 넣지 않고 설치 측에서 설정한다.
- 번들 서버 description은 같은 plugin의 `org.opspresso.agent-studio/mcp/<name>.md`에 둔다.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ Agent Memory의 `remember`·`recall`·`forget` 계약이다. 배포된 서버의
해당 기억을 확인한다. 검색 결과가 없다는 것을 자료가 전혀 없다는 뜻으로 해석하지 않는다.
- 다음 작업에도 유효한 사실은 저장 권한과 공유 범위를 확인하고 remember로 저장한다.
kind, scope, title, content, source를 실제 schema에 맞춰 전달한다.
remember는 신규 생성이므로 응답이 불확실할 때 같은 내용을 무조건 다시 저장하지 않는다.
idempotencyKey가 제공되면 같은 저장 요청의 키와 입력을 유지한다. 키 없는 신규 생성의
응답이 불확실할 때 같은 내용을 무조건 다시 저장하지 않는다.
- scope.kind는 organization, team, user 중 요청에 맞게 명시한다.
개인·팀 범위가 거부되면 조직 전체에 대신 저장하지 않는다. 대화 전용 scope는 없다.
- 잊기 요청은 실제 조회·저장 결과의 memory ID와 현재 version을 확인한 뒤
Expand All @@ -30,7 +31,10 @@ Agent Memory의 `remember`·`recall`·`forget` 계약이다. 배포된 서버의
Agent Memory는 설치 측에서 별도 MCP로 등록한다. 실제 런에 제공되지 않으면 기억
조회·저장을 약속하지 않고 현재 대화의 자료로 진행한다. `document_search`는 처리된
문서 chunk, `knowledge_search`·`knowledge_neighborhood`는 그래프 근거를 찾는다.
문서 업로드·기억 본문 수정·Graph 작성은 MCP에 없으므로 관리 화면이나 별도 API의 작업이다.
문서 수집 도구가 제공되면 document_ingest로 개인·팀·조직 scope의 텍스트를 저장하고
document_ingest_status로 ready를 확인한다. 수집에는 idempotencyKey가 필수이며 접수와
처리 완료를 구분한다. 도구가 없는 구버전의 문서 수집, 기억 본문 수정과 Graph 작성은
관리 화면이나 별도 API의 작업이다.
검색 결과의 문서 ID는 호스트 앱의 `File` artifact ID가 아니다.

`recall`의 text에는 ID와 version이 있지만 항목당 1,200자·전체 4,000자로 잘린다.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,38 +28,12 @@
<button type="button" data-move="-1">이전</button>
<p class="progress" role="status"></p>
<button type="button" data-move="1">다음</button>
<button type="button" class="reset">처음으로</button>
</nav>
```

```js
(function () {
var steps = Array.prototype.slice.call(document.querySelectorAll('.step'));
var progress = document.querySelector('.progress');
var prev = document.querySelector('[data-move="-1"]');
var next = document.querySelector('[data-move="1"]');
var at = 0;

function show(index, moveFocus) {
at = Math.max(0, Math.min(steps.length - 1, index));
steps.forEach(function (step, i) { step.hidden = i !== at; });
prev.disabled = at === 0;
next.disabled = at === steps.length - 1;
progress.textContent = (at + 1) + ' / ' + steps.length + ' · ' + steps[at].dataset.title;
if (moveFocus) { steps[at].focus(); }
}

prev.addEventListener('click', function () { show(at - 1, true); });
next.addEventListener('click', function () { show(at + 1, true); });

document.addEventListener('keydown', function (event) {
if (event.target.closest('input, textarea, select')) { return; }
if (event.key === 'ArrowRight') { show(at + 1, true); }
if (event.key === 'ArrowLeft') { show(at - 1, true); }
});

show(0, false); // 여기서 처음으로 hidden 이 걸린다. 스크립트가 없으면 전부 보인다
})();
```
단계 전환·키보드·focus 구현은 [템플릿](template.md)의 `show`와 이벤트 처리를 사용한다.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
처리한 화살표 키는 기본 스크롤을 막고 입력 요소·편집 영역·조합 단축키는 가로채지 않는다.

인쇄에서는 전 단계를 되살린다.

Expand Down Expand Up @@ -162,7 +136,7 @@ document.querySelectorAll('.comparison').forEach(function (comparison) {
- 터치 대상은 44px 이상으로 둔다.

```html
<div class="figure">
<div class="stage">
<svg viewBox="0 0 480 260" role="img" aria-labelledby="fig-t fig-d">
<title id="fig-t">요청이 갈림길을 지나 서버 세 대로 나뉜다</title>
<desc id="fig-d">왼쪽에서 들어온 화살표가 가운데 갈림길에서 셋으로 갈라진다.</desc>
Expand All @@ -177,24 +151,26 @@ document.querySelectorAll('.comparison').forEach(function (comparison) {
document.querySelectorAll('.hotspot').forEach(function (spot) {
spot.addEventListener('click', function () {
var on = spot.getAttribute('aria-pressed') === 'true';
var figure = spot.closest('.figure');
figure.querySelectorAll('.hotspot').forEach(function (other) {
var stage = spot.closest('.stage');
stage.querySelectorAll('.hotspot').forEach(function (other) {
other.setAttribute('aria-pressed', 'false');
});
spot.setAttribute('aria-pressed', on ? 'false' : 'true');
figure.querySelectorAll('svg [data-part]').forEach(function (part) {
stage.querySelectorAll('svg [data-part]').forEach(function (part) {
part.classList.toggle('dimmed', !on && part.dataset.part !== spot.dataset.part);
});
});
});
```

```css
.figure svg .dimmed { opacity: .25; }
.stage { position: relative; }
.stage svg .dimmed { opacity: .25; }
```

각 비교 대상은 `<g data-part="router">`처럼 묶는다. 라벨을 해당 그룹에 넣고,
다른 대상을 감싸는 상위 그룹에는 `data-part`를 중복 지정하지 않는다.
버튼 크기·위치·focus 스타일은 템플릿의 `.hotspot` CSS를 사용한다.

## 5. 직접 해보기

Expand Down
9 changes: 6 additions & 3 deletions plugins/design/skills/html-explainer/references/template.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,12 @@ button:focus-visible, input:focus-visible { outline: 2px solid var(--accent); ou
next.addEventListener('click', function () { show(at + 1, true); });

document.addEventListener('keydown', function (event) {
if (event.target.closest('input, textarea, select')) { return; }
if (event.key === 'ArrowRight') { show(at + 1, true); }
if (event.key === 'ArrowLeft') { show(at - 1, true); }
if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey ||
event.target.closest('input, textarea, select, [contenteditable]')) { return; }
if (event.key === 'ArrowRight' || event.key === 'ArrowLeft') {
event.preventDefault();
show(at + (event.key === 'ArrowRight' ? 1 : -1), true);
}
});

// 슬라이더 — 값이 바뀌면 그림이 즉시 반응한다
Expand Down
14 changes: 8 additions & 6 deletions plugins/design/skills/html-report/references/template.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,20 +318,22 @@ sup a { color: var(--brand-light); text-decoration: none; padding: 0 .1em; }
var index = Array.prototype.indexOf.call(th.parentNode.children, th);
var numeric = th.dataset.sort === 'num';
var asc = th.getAttribute('aria-sort') !== 'ascending';
var rows = Array.prototype.slice.call(body.rows);
// Read DOM text and parse numeric keys once; comparisons use cached values.
var rows = Array.prototype.map.call(body.rows, function (row) {
var cell = row.cells[index];
return { row: row, value: numeric ? numericValue(cell) : cell.textContent.trim() };
});
rows.sort(function (a, b) {
var x = a.cells[index].textContent.trim();
var y = b.cells[index].textContent.trim();
var x = a.value;
var y = b.value;
if (numeric) {
x = numericValue(a.cells[index]);
y = numericValue(b.cells[index]);
if (x === null) { return y === null ? 0 : 1; }
if (y === null) { return -1; }
return asc ? x - y : y - x;
}
return asc ? collator.compare(x, y) : collator.compare(y, x);
});
rows.forEach(function (row) { body.appendChild(row); });
rows.forEach(function (entry) { body.appendChild(entry.row); });
table.querySelectorAll('th').forEach(function (other) { other.removeAttribute('aria-sort'); });
th.setAttribute('aria-sort', asc ? 'ascending' : 'descending');
}
Expand Down
27 changes: 11 additions & 16 deletions plugins/execution/skills/sandbox-task/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,13 @@ compatibility: >
선택된 Workspace가 있으면 ID를 생략한 `run`으로 이어간다. `start`를 반복해도 새 작업이 접수되지 않는다.
원격 자료를 읽는 것만으로 충분한 작업에는 Sandbox를 만들지 않는다. 실제 파일 처리나 검증이 필요할 때 사용한다.
파일은 `workdir`의 상대 경로에 쓴다. `workspace_path`는 웹 링크이며 `cd` 대상이 아니다.
`command`는 `task` 문자열 전체를
스크립트로 실행한다. 자연어 요청을 쉘 스크립트 자리에 넣지 않는다. Runtime 지정이 없으면 options의
default_runtime을 따른다. 코딩 Runtime에는 완결된 자연어 `task`를 전달하고 command에는 실제 실행할 셸을 구성한다. 실행 스크립트가 이미 주어졌거나
검증된 짧은 명령이 확정된 경우에 command를 사용한다. CLI 프로젝트를 만드는 작업은 command 선택의 이유가 아니다.
command 오류가 나면 task에 설명·Markdown이 들어갔는지 먼저 확인하며 설치·언어 문제로 단정하지 않는다. 기존 Workspace가 있으면
`run`으로 이어가며, ID가 없는 새 작업만 `start`를 쓴다. Git 없는 작업에는 저장소와 브랜치를
모두 `null`로 전달한다. `wait`·`status`의 실제 결과로 완료를 판단한다.
셸은 `-eu`로 실행된다. 실패를 의도적으로 처리할 경우 조건문으로 명시하고, 실패한 `cd` 이후
다른 위치에서 쓰기를 계속하지 않는다. 코드 구현에는 허용된 Native 코딩 Runtime을 우선 사용한다.
Runtime 미지정이면 options.default_runtime을 따른다. 코딩 Runtime에는 완결된 자연어 `task`를
전달한다. `command`는 task 전체를 셸로 실행하므로 이미 작성된 스크립트나 검증된 짧은 명령에
사용한다. CLI 프로젝트 생성도 자연어 코딩 작업이다. command 오류는 task에 설명·Markdown이
들어갔는지 먼저 확인한다. 셸은 `-eu`로 실행되므로 의도적인 실패 처리는 조건문으로 명시하고
실패한 `cd` 뒤에 쓰기를 계속하지 않는다.
선택이 없는 새 작업만 `start`를 쓰며 Git 없는 작업은 저장소와 브랜치를 모두 `null`로 전달한다.
`wait`·`status`의 실제 결과로 완료를 판단한다.

## 작업 종류에 맞게 검증한다

Expand All @@ -49,13 +47,10 @@ command 오류가 나면 task에 설명·Markdown이 들어갔는지 먼저 확
제공된 파일·텍스트에서 시작하고, 문서 편집·다운로드는 현재 제공된 File/SaveFile 등의 계약을 따른다.
- Git 작업: 최종 Diff와 변경 파일 목록을 읽어 요청 밖 변경을 제거한다.
커밋·push·PR·배포는 별도 명시적 사용자 요청과 해당 Runtime의 승인 기능을 따른다.
호스트 앱의 커밋·push·PR·main 병합은 `Workspace.prepare_git`로 검토를 준비하고 `approval_path`에서 승인한다.
`pull-request`는 title/body/draft를 받는다. `merge`의 pullRequestNumber/headSha에는 status.pull_request의 number/headSha를 넣는다.
PR 없이 main 푸시를 명시적으로 요청하면 작업 브랜치 푸시 후 `push-main`을 준비한다. fast-forward만 허용한다.
PR 생성 때문에 native task를 실행하거나 Workspace를 닫고 다시 만들지 않는다. 종료된 Workspace도
`prepare_git`가 복원한다. 게시·PR 요청에는 `workspace-task`의 검토와 게시 절차를 따른다.
Native Runtime에 Git 쓰기를 시키지 않는다. `/control/git`·`index.lock` 권한 거절에는
임시 인덱스·권한 변경·GitHub 쓰기 도구로 재시도하지 않고 승인 경로를 안내한다.
호스트 앱에서는 같은 공간의 `Workspace.prepare_git`로 검토하고 반환된 승인 링크를 전달한다.
종료된 공간도 복원되므로 게시를 위해 새 native task나 Workspace를 만들지 않는다.
연결된 `workspace-task`가 있으면 게시 절차를 읽고, 없으면 실제 schema로 요청된 동작만 준비한다.
Native Git 쓰기나 임시 index·권한 변경·GitHub 쓰기로 승인 경계를 우회하지 않는다.

Sandbox의 출력 경로를 호스트 파일이나 Artifact URL로 표현하지 않는다. 다운로드 도구가 실제로
제공되지 않으면 Workspace 링크·파일 경로와 확인한 내용을 알려 준다.
Expand Down
2 changes: 1 addition & 1 deletion plugins/execution/skills/workspace-task/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Workspace는 파일·Git·Session을 유지하는 작업 공간이고 Sandbox는
2. 선택된 공간은 `run`으로 이어간다. `workspace_id`를 생략할 수 있다. 사용자가 다른 기존 공간을
지정했을 때만 `use_workspace`로 선택한다. start를 반복해도 새 작업이 접수되지 않는다.
3. 선택이 없을 때 `start`에 runtime, repository, base_branch, task를 보낸다. 저장소 작업은 두 Git
선택 값을 모두 지정한다. 둘 다 null이면 Git-free다. 기본 저장소는 없으며 Runtime 미지정 시 options의 default_runtime을 따른다.
선택 값을 모두 지정한다. 둘 다 null이면 Git-free이며 기본 저장소는 없다.
4. 사용자가 Runtime을 지정하지 않으면 options의 default_runtime을 따른다. 현재 허용 Runtime 목록에
없으면 Models의 모델 연결 또는 프로젝트 기본 Runtime 설정을 확인한다. command에는 실제 실행할
셸 스크립트를 구성해서 보내며 자연어 목록을 넣지 않는다. 코딩 Runtime에는 완결된 자연어 task를 보낸다.
Expand Down
Loading
Loading