openclacky 1.3.6 → 1.3.7
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -0
- data/lib/clacky/agent.rb +8 -1
- data/lib/clacky/agent_profile.rb +121 -53
- data/lib/clacky/brand_config.rb +309 -6
- data/lib/clacky/cli.rb +4 -57
- data/lib/clacky/default_extensions/coding/ext.yml +18 -0
- data/lib/clacky/default_extensions/ext-studio/agents/ext-developer/system_prompt.md +95 -0
- data/lib/clacky/default_extensions/ext-studio/api/handler.rb +254 -0
- data/lib/clacky/default_extensions/ext-studio/ext.yml +32 -0
- data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +1269 -0
- data/lib/clacky/default_extensions/ext-studio/skills/ext-debug/SKILL.md +71 -0
- data/lib/clacky/default_extensions/ext-studio/skills/ext-publish/SKILL.md +74 -0
- data/lib/clacky/default_extensions/ext-studio/skills/ext-scaffold/SKILL.md +65 -0
- data/lib/clacky/default_extensions/general/ext.yml +18 -0
- data/lib/clacky/default_extensions/git/ext.yml +12 -0
- data/lib/clacky/{default_agents/_panels/git/panel.js → default_extensions/git/panels/git/view.js} +1 -2
- data/lib/clacky/default_extensions/meeting/{handler.rb → api/handler.rb} +78 -26
- data/lib/clacky/default_extensions/meeting/ext.yml +15 -0
- data/lib/clacky/default_extensions/meeting/{meeting.js → panels/meeting/view.js} +266 -89
- data/lib/clacky/default_extensions/meeting/skills/meeting-summarizer/SKILL.md +2 -1
- data/lib/clacky/default_extensions/time_machine/ext.yml +12 -0
- data/lib/clacky/{default_agents/_panels/time_machine/panel.js → default_extensions/time_machine/panels/time_machine/view.js} +58 -47
- data/lib/clacky/default_skills/channel-manager/discord_setup.rb +1 -1
- data/lib/clacky/default_skills/extend-openclacky/SKILL.md +83 -18
- data/lib/clacky/{api_extension.rb → extension/api_extension.rb} +27 -11
- data/lib/clacky/extension/api_loader.rb +136 -0
- data/lib/clacky/extension/cli_commands.rb +226 -0
- data/lib/clacky/{server/api_extension_dispatcher.rb → extension/dispatcher.rb} +22 -9
- data/lib/clacky/extension/hook_loader.rb +77 -0
- data/lib/clacky/extension/loader.rb +483 -0
- data/lib/clacky/extension/packager.rb +226 -0
- data/lib/clacky/{patch_loader.rb → extension/patch_loader.rb} +45 -0
- data/lib/clacky/extension/scaffold/template_renderer.rb +64 -0
- data/lib/clacky/extension/scaffold/templates/full/README.md.erb +48 -0
- data/lib/clacky/extension/scaffold/templates/full/agents/designer.md +5 -0
- data/lib/clacky/extension/scaffold/templates/full/api/handler.rb.erb +16 -0
- data/lib/clacky/extension/scaffold/templates/full/channels/noop.rb.erb +40 -0
- data/lib/clacky/extension/scaffold/templates/full/ext.yml.erb +41 -0
- data/lib/clacky/extension/scaffold/templates/full/hooks/audit.rb +9 -0
- data/lib/clacky/extension/scaffold/templates/full/panels/dashboard/view.js.erb +194 -0
- data/lib/clacky/extension/scaffold/templates/full/patches/audit.rb +15 -0
- data/lib/clacky/extension/scaffold/templates/full/skills/__slug__-skill/SKILL.md.erb +8 -0
- data/lib/clacky/extension/scaffold/templates/hello/api/handler.rb.erb +9 -0
- data/lib/clacky/extension/scaffold/templates/hello/ext.yml.erb +12 -0
- data/lib/clacky/extension/scaffold/templates/hello/panels/hello/view.js.erb +31 -0
- data/lib/clacky/extension/scaffold.rb +55 -0
- data/lib/clacky/extension/verifier.rb +196 -0
- data/lib/clacky/identity.rb +70 -0
- data/lib/clacky/locales/en.rb +19 -1
- data/lib/clacky/locales/zh.rb +19 -1
- data/lib/clacky/platform_http_client.rb +18 -15
- data/lib/clacky/server/channel/extension_adapter_loader.rb +32 -0
- data/lib/clacky/server/channel.rb +6 -0
- data/lib/clacky/server/http_server.rb +313 -144
- data/lib/clacky/server/session_registry.rb +9 -2
- data/lib/clacky/skill_loader.rb +28 -1
- data/lib/clacky/ui2/ui_controller.rb +9 -6
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +748 -573
- data/lib/clacky/web/app.js +63 -15
- data/lib/clacky/web/auth.js +2 -0
- data/lib/clacky/web/components/notify.js +2 -0
- data/lib/clacky/web/components/sidebar.js +1 -5
- data/lib/clacky/web/core/ext.js +316 -79
- data/lib/clacky/web/features/backup/store.js +1 -0
- data/lib/clacky/web/features/backup/view.js +0 -1
- data/lib/clacky/web/features/brand/view.js +13 -5
- data/lib/clacky/web/features/extensions/store.js +184 -0
- data/lib/clacky/web/features/extensions/view.js +389 -0
- data/lib/clacky/web/features/new-session/store.js +186 -0
- data/lib/clacky/web/features/new-session/view.js +319 -0
- data/lib/clacky/web/features/share/view.js +63 -14
- data/lib/clacky/web/features/skills/store.js +3 -1
- data/lib/clacky/web/features/skills/view.js +1 -1
- data/lib/clacky/web/features/tasks/view.js +12 -0
- data/lib/clacky/web/features/trash/store.js +24 -6
- data/lib/clacky/web/features/trash/view.js +46 -9
- data/lib/clacky/web/features/workspace/store.js +2 -0
- data/lib/clacky/web/features/workspace/view.js +0 -1
- data/lib/clacky/web/i18n.js +118 -106
- data/lib/clacky/web/index.html +138 -188
- data/lib/clacky/web/sessions.js +64 -269
- data/lib/clacky/web/settings.js +1 -1
- data/lib/clacky/web/skills.js +2 -0
- data/lib/clacky/web/theme.js +2 -0
- data/lib/clacky/web/ws-dispatcher.js +1 -0
- data/lib/clacky/web/ws.js +2 -0
- data/lib/clacky.rb +21 -10
- metadata +55 -24
- data/benchmark/runner.rb +0 -441
- data/lib/clacky/api_extension_loader.rb +0 -168
- data/lib/clacky/default_agents/SOUL.md +0 -3
- data/lib/clacky/default_agents/USER.md +0 -1
- data/lib/clacky/default_agents/coding/profile.yml +0 -5
- data/lib/clacky/default_agents/coding/webui/.gitkeep +0 -0
- data/lib/clacky/default_agents/general/profile.yml +0 -2
- data/lib/clacky/default_extensions/meeting/meta.yml +0 -3
- data/lib/clacky/web/features/creator/store.js +0 -81
- data/lib/clacky/web/features/creator/view.js +0 -380
- /data/lib/clacky/{default_agents → default_extensions/coding/agents}/coding/system_prompt.md +0 -0
- /data/lib/clacky/{default_agents → default_extensions/general/agents}/general/system_prompt.md +0 -0
- /data/lib/clacky/{default_agents/base_prompt.md → prompts/base.md} +0 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ext-debug
|
|
3
|
+
description: Debug an OpenClacky extension that won't load, throws errors, or whose panel/api/skill/agent isn't showing up. Use when the user reports something broken with their extension, when `clacky ext verify` reports issues, or when a change didn't take effect. Reads structured verify errors and fixes manifest and file problems.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Extension Debug
|
|
7
|
+
|
|
8
|
+
Diagnose and fix a broken extension. Your primary instrument is `clacky ext verify` —
|
|
9
|
+
it's a compiler for extensions: every issue is structured with a `code`, `message`,
|
|
10
|
+
the offending `file`, and a `hint` telling you how to fix it.
|
|
11
|
+
|
|
12
|
+
## Step 1 — Run verify
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
clacky ext verify
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Read the output line by line. `[OK]` lines confirm resolved units. `[ERR]` blocks a
|
|
19
|
+
load; `[WARN]` is advisory. Each issue looks like:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
[ERR] <ext> <unit> (<code>) — <message> [<file>]
|
|
23
|
+
hint: <how to fix>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Step 2 — Fix by error code
|
|
27
|
+
|
|
28
|
+
- **`loader.error`** — a file the manifest points at is missing or the container
|
|
29
|
+
couldn't be parsed. Check the `file` path exists and `ext.yml` is valid YAML. A skill
|
|
30
|
+
needs `SKILL.md` under `skills/<id>/`; an agent needs its `prompt` file; a panel needs
|
|
31
|
+
its `view` file; api needs `api/handler.rb`.
|
|
32
|
+
- **`schema.unknown_contributes`** — a top-level key under `contributes:` isn't one of
|
|
33
|
+
`panels api skills agents channels patches hooks`. Fix the spelling.
|
|
34
|
+
- **`schema.unknown_field` / `schema.unknown_key`** — a unit has a field that isn't
|
|
35
|
+
allowed for its type. Remove or rename it. Allowed fields:
|
|
36
|
+
- panel: `id title title_zh description description_zh view order attach`
|
|
37
|
+
- api: `id handler`
|
|
38
|
+
- skill: `id dir protected`
|
|
39
|
+
- agent: `id title title_zh description description_zh order prompt panels skills`
|
|
40
|
+
- channel: `id platform adapter`
|
|
41
|
+
- **`schema.bad_attach`** — a panel `attach:` entry isn't a valid token. Use agent ids
|
|
42
|
+
or `"*"` for all.
|
|
43
|
+
- **`ref.missing_panel`** — an agent references `panels: [id]` that no panel provides.
|
|
44
|
+
Fix the id, or use `<ext_id>/<panel_id>` to reference a panel in another extension.
|
|
45
|
+
- **`ref.missing_skill`** — an agent references `skills: [id]` that no skill provides.
|
|
46
|
+
Fix the id or add the skill.
|
|
47
|
+
- **`ref.missing_attach_agent`** — a panel's `attach:` names an agent that doesn't
|
|
48
|
+
exist. Fix the agent id.
|
|
49
|
+
- **`override`** (warning) — a unit in a higher layer is shadowing a lower one
|
|
50
|
+
(`local > installed > builtin`). Usually intentional; confirm with the user if not.
|
|
51
|
+
|
|
52
|
+
Fix one issue, re-run verify, repeat until clean.
|
|
53
|
+
|
|
54
|
+
## Step 3 — "It verifies but doesn't show up"
|
|
55
|
+
|
|
56
|
+
If verify is clean but a change isn't visible:
|
|
57
|
+
- **Hot reload is per-request.** After editing `view.js`, `handler.rb`, or a `SKILL.md`,
|
|
58
|
+
the user must **reload the WebUI page** — there's no restart, but a stale tab won't
|
|
59
|
+
update on its own. Editing `ext.yml` also applies on the next load.
|
|
60
|
+
- **Panel not appearing?** Check the panel's `attach:` (or the agent that references it
|
|
61
|
+
via `panels: [id]`). A panel with no `attach` and no referencing agent has nothing to
|
|
62
|
+
mount onto.
|
|
63
|
+
- **API 404?** Routes are relative to `/api/ext/<ext_id>/`. Confirm the handler subclasses
|
|
64
|
+
`Clacky::ApiExtension` and the route pattern matches what `view.js` fetches.
|
|
65
|
+
- **Skill not triggering?** The AI selects skills by their `description`. Make the
|
|
66
|
+
description concrete about WHEN to use it.
|
|
67
|
+
|
|
68
|
+
## Step 4 — Confirm the fix
|
|
69
|
+
|
|
70
|
+
End with a clean `clacky ext verify` and have the user reload to confirm the behavior
|
|
71
|
+
actually works — don't declare success on "should work."
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ext-publish
|
|
3
|
+
description: Pack and publish an OpenClacky extension to the marketplace, publish a new version, list published extensions, or unpublish one. Use when the user wants to ship, release, publish, update, or take down an extension. Handles packing, the license requirement, versioning, and the already-published case.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Extension Publish
|
|
7
|
+
|
|
8
|
+
Take a finished local extension and ship it to the OpenClacky marketplace, then confirm
|
|
9
|
+
it's live.
|
|
10
|
+
|
|
11
|
+
## Before publishing
|
|
12
|
+
|
|
13
|
+
- The extension must live in the **local** layer (`~/.clacky/ext/local/<id>/`). Only
|
|
14
|
+
local containers can be packed. Marketplace-origin and encrypted (`SKILL.md.enc`)
|
|
15
|
+
containers are rejected.
|
|
16
|
+
- Publishing requires an **activated user license** — it proves creator identity, and
|
|
17
|
+
the platform attributes the extension to that account. If activation is missing, tell
|
|
18
|
+
the user to activate first; don't try to work around it.
|
|
19
|
+
- Run `clacky ext verify` one last time and confirm no errors before shipping.
|
|
20
|
+
|
|
21
|
+
## Publish (first time)
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
clacky ext publish <id>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This packs the local container into a zip and uploads it. On success you'll see:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
Published <id> v<version> → status=<status>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Options:
|
|
34
|
+
- `--status draft` — publish as a draft (not yet visible on the public marketplace).
|
|
35
|
+
Omit or use `--status published` to go live.
|
|
36
|
+
- `--changelog "..."` — release notes for this version.
|
|
37
|
+
|
|
38
|
+
## Publish a new version
|
|
39
|
+
|
|
40
|
+
If the extension is already published, a plain `publish` fails with:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
Error: <id> already published. Re-run with --force to publish a new version.
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Re-run with `--force` (and ideally a `--changelog`) to publish a new version. The patch
|
|
47
|
+
version auto-increments on the platform side.
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
clacky ext publish <id> --force --changelog "Fixed the weather refresh bug"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## List your published extensions
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
clacky ext published
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Shows each extension with its latest version, status, and unit summary.
|
|
60
|
+
|
|
61
|
+
## Unpublish
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
clacky ext unpublish <id>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Soft-deletes (takes down) one of your published extensions. Confirm with the user
|
|
68
|
+
before doing this — it removes it from the marketplace.
|
|
69
|
+
|
|
70
|
+
## Wrap up
|
|
71
|
+
|
|
72
|
+
After a successful publish, tell the user the version and status in plain terms, and
|
|
73
|
+
mention they can run `clacky ext published` to see it in their list, or bump a new
|
|
74
|
+
version anytime with `--force`.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ext-scaffold
|
|
3
|
+
description: Scaffold a new OpenClacky extension from an idea. Use when the user wants to create, start, or bootstrap a new extension, plugin, panel, agent, or skill container. Maps the idea to the right contributes types and generates a working skeleton in the local layer.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Extension Scaffold
|
|
7
|
+
|
|
8
|
+
Turn a plain-language idea into a working extension skeleton, then read the generated
|
|
9
|
+
files so you know what you're working with.
|
|
10
|
+
|
|
11
|
+
## Step 1 — Understand the idea
|
|
12
|
+
|
|
13
|
+
Figure out what the extension should DO and which contributes types it needs. Ask one
|
|
14
|
+
clarifying question only if it's genuinely ambiguous. Common mappings:
|
|
15
|
+
|
|
16
|
+
- "Show me X in a side panel / add a button / dashboard" → **panel** (+ **api** if it
|
|
17
|
+
needs a backend or to call an external service).
|
|
18
|
+
- "A capability the AI can invoke" (summarize, translate, format) → **skill**.
|
|
19
|
+
- "A specialized assistant with its own personality/tools" → **agent** (usually
|
|
20
|
+
bundling its own panels/skills).
|
|
21
|
+
- "Connect to Slack / an in-house IM" → **channel**.
|
|
22
|
+
|
|
23
|
+
Keep it minimal. Most useful extensions are one panel + one handler, or one skill.
|
|
24
|
+
Do NOT add `patches` or `hooks` unless the user explicitly asks — they run arbitrary
|
|
25
|
+
Ruby and carry supply-chain risk.
|
|
26
|
+
|
|
27
|
+
## Step 2 — Generate the skeleton
|
|
28
|
+
|
|
29
|
+
Pick a lowercase, hyphenated id derived from the idea (e.g. `weather-panel`).
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
clacky ext new <id>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This creates `~/.clacky/ext/local/<id>/` with a working hello panel + handler:
|
|
36
|
+
- `ext.yml` — the manifest
|
|
37
|
+
- `panels/hello/view.js` — a panel that pings the backend
|
|
38
|
+
- `api/handler.rb` — a `Clacky::ApiExtension` subclass mounted at `/api/ext/<id>/`
|
|
39
|
+
|
|
40
|
+
Use `--full` only if the user needs the kitchen-sink reference exercising all seven
|
|
41
|
+
contributes types — it's a lot to read, so prefer the plain scaffold otherwise.
|
|
42
|
+
|
|
43
|
+
## Step 3 — Read what was generated
|
|
44
|
+
|
|
45
|
+
Always read the generated `ext.yml`, `view.js`, and `handler.rb` before editing. This
|
|
46
|
+
is your starting point; you'll reshape it to match the idea.
|
|
47
|
+
|
|
48
|
+
## Step 4 — Reshape to the idea
|
|
49
|
+
|
|
50
|
+
Edit the files into real, working code:
|
|
51
|
+
- Rename the panel id and `view.js` path to match the feature.
|
|
52
|
+
- Update `ext.yml` `contributes:` — add `skills:`/`agents:` blocks if needed. A skill
|
|
53
|
+
is a `SKILL.md` under `skills/<id>/`; an agent is a `system_prompt.md` that can
|
|
54
|
+
reference `panels: [id]` and `skills: [id]`.
|
|
55
|
+
- In `view.js`, reuse host CSS classes (`btn-primary`, `btn-secondary`, `form-input`,
|
|
56
|
+
`form-textarea`, `form-label`) so the panel inherits the theme automatically.
|
|
57
|
+
- In `handler.rb`, define routes relative to the `/api/ext/<id>/` mount.
|
|
58
|
+
|
|
59
|
+
## Step 5 — Confirm it loads
|
|
60
|
+
|
|
61
|
+
Run `clacky ext verify` and confirm the new units resolve with no errors. Then tell
|
|
62
|
+
the user to reload the WebUI page — panels and api changes are live on the next request,
|
|
63
|
+
no restart needed.
|
|
64
|
+
|
|
65
|
+
If verify reports problems, switch to debugging (the ext-debug skill covers this).
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
id: general
|
|
2
|
+
name: General
|
|
3
|
+
description: General-purpose digital employee agent
|
|
4
|
+
version: "0.1.0"
|
|
5
|
+
author: OpenClacky
|
|
6
|
+
homepage: https://www.openclacky.com
|
|
7
|
+
license: MIT
|
|
8
|
+
origin: self
|
|
9
|
+
contributes:
|
|
10
|
+
agents:
|
|
11
|
+
- id: general
|
|
12
|
+
title: General
|
|
13
|
+
title_zh: 通用
|
|
14
|
+
description: A versatile digital employee living on your computer
|
|
15
|
+
description_zh: 驻留在你电脑里的多面手数字员工
|
|
16
|
+
order: 1
|
|
17
|
+
prompt: agents/general/system_prompt.md
|
|
18
|
+
panels: [meeting/meeting]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
id: git
|
|
2
|
+
name: Git
|
|
3
|
+
description: Changes panel — a friendly view of what the AI changed in the working tree
|
|
4
|
+
version: "0.1.0"
|
|
5
|
+
author: OpenClacky
|
|
6
|
+
homepage: https://www.openclacky.com
|
|
7
|
+
license: MIT
|
|
8
|
+
origin: self
|
|
9
|
+
contributes:
|
|
10
|
+
panels:
|
|
11
|
+
- id: git
|
|
12
|
+
view: panels/git/view.js
|
data/lib/clacky/{default_agents/_panels/git/panel.js → default_extensions/git/panels/git/view.js}
RENAMED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// "改动 / Changes": a non-technical view of what the AI changed, backed by the
|
|
4
4
|
// built-in git API (GET/POST /api/sessions/:id/git/*). Mounted as a tab in the
|
|
5
|
-
// "session.aside" slot,
|
|
5
|
+
// "session.aside" slot, attached to agents via `attach: [coding]` in ext.yml.
|
|
6
6
|
//
|
|
7
7
|
// Deliberately hides git jargon: no porcelain status codes (M/??), no
|
|
8
8
|
// branch/ahead/behind unless the branch is NOT the main line (main/master) —
|
|
@@ -194,7 +194,6 @@
|
|
|
194
194
|
refresh(ctx.sessionId, root, body, ctx);
|
|
195
195
|
return root;
|
|
196
196
|
}, {
|
|
197
|
-
panel: "git",
|
|
198
197
|
order: 10,
|
|
199
198
|
tab: { id: "changes", label: () => t("changes.tab") },
|
|
200
199
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
|
+
require "set"
|
|
4
5
|
require "fileutils"
|
|
5
6
|
require "base64"
|
|
6
7
|
require "tmpdir"
|
|
@@ -12,7 +13,10 @@ class MeetingExt < Clacky::ApiExtension
|
|
|
12
13
|
|
|
13
14
|
MEETINGS_ROOT = File.expand_path("~/.clacky/meetings")
|
|
14
15
|
VOCABULARY_PATH = File.join(MEETINGS_ROOT, "vocabulary.txt")
|
|
15
|
-
|
|
16
|
+
# Always injected into STT vocabulary — these are our own wake words / brand
|
|
17
|
+
# names and must not be droppable by the user's saved list.
|
|
18
|
+
SYSTEM_VOCABULARY = "Clacky, 小克".freeze
|
|
19
|
+
DEFAULT_VOCABULARY = "Clacky, 小克, OpenClacky, openclacky"
|
|
16
20
|
|
|
17
21
|
# annotate is a read-only analysis: block every side-effecting tool so the
|
|
18
22
|
# forked subagent can only read/think, never write files, run commands,
|
|
@@ -56,12 +60,15 @@ class MeetingExt < Clacky::ApiExtension
|
|
|
56
60
|
end
|
|
57
61
|
|
|
58
62
|
# POST /api/ext/meeting/end
|
|
59
|
-
# body: { session_id, meeting_id }
|
|
63
|
+
# body: { session_id, meeting_id, display_message? }
|
|
60
64
|
# Finalizes the meeting and triggers summarization via the session agent.
|
|
61
65
|
post "/end" do
|
|
62
66
|
sid, mid = json_body.values_at("session_id", "meeting_id")
|
|
63
67
|
error!("session_id and meeting_id required", status: 422) unless sid && mid
|
|
64
68
|
|
|
69
|
+
display_message = json_body["display_message"].to_s
|
|
70
|
+
display_message = "🛑 Meeting ended — generating meeting minutes…" if display_message.strip.empty?
|
|
71
|
+
|
|
65
72
|
dir = meeting_dir(sid, mid)
|
|
66
73
|
error!("meeting not found", status: 404) unless File.directory?(dir)
|
|
67
74
|
|
|
@@ -71,33 +78,19 @@ class MeetingExt < Clacky::ApiExtension
|
|
|
71
78
|
File.write(meta_path, JSON.pretty_generate(meta))
|
|
72
79
|
|
|
73
80
|
transcript_path = File.join(dir, "transcript.jsonl")
|
|
74
|
-
lines = File.readlines(transcript_path).map { |l| JSON.parse(l)["text"] }.reject
|
|
81
|
+
lines = File.readlines(transcript_path).map { |l| JSON.parse(l)["text"] }.reject { |t| t.nil? || t.strip.empty? }
|
|
75
82
|
transcript = lines.join("\n")
|
|
76
83
|
|
|
77
84
|
logger.info("end: sid=#{sid} mid=#{mid} lines=#{lines.size} transcript_len=#{transcript.length}")
|
|
78
85
|
|
|
79
|
-
if transcript.strip.empty?
|
|
80
|
-
logger.warn("end: transcript is empty, skipping summarization")
|
|
81
|
-
json(ok: true, meeting_id: mid, skipped: true)
|
|
82
|
-
next
|
|
83
|
-
end
|
|
84
|
-
|
|
85
86
|
prompt = <<~PROMPT
|
|
86
|
-
A meeting just ended.
|
|
87
|
+
A meeting just ended. Your ONLY next action is to call the tool `invoke_skill` with `name: "meeting-summarizer"` and pass the transcript below as the input. Do not use any other tool. Do not answer directly. Do not open a browser or run shell commands.
|
|
87
88
|
|
|
88
89
|
Transcript:
|
|
89
90
|
#{transcript}
|
|
90
91
|
PROMPT
|
|
91
92
|
|
|
92
|
-
|
|
93
|
-
submit_task(sid, prompt, display_message: "🛑 Meeting ended — generating meeting minutes…")
|
|
94
|
-
logger.info("end: submit_task succeeded sid=#{sid}")
|
|
95
|
-
rescue => e
|
|
96
|
-
logger.error("end: submit_task failed sid=#{sid} error=#{e.message}")
|
|
97
|
-
json(ok: false, meeting_id: mid, error: e.message)
|
|
98
|
-
next
|
|
99
|
-
end
|
|
100
|
-
|
|
93
|
+
submit_task(sid, prompt, display_message: display_message, interrupt: true)
|
|
101
94
|
json(ok: true, meeting_id: mid)
|
|
102
95
|
end
|
|
103
96
|
|
|
@@ -116,16 +109,19 @@ class MeetingExt < Clacky::ApiExtension
|
|
|
116
109
|
|
|
117
110
|
mime = json_body["mime_type"].to_s.split(";").first.strip
|
|
118
111
|
mime = "audio/webm" if mime.empty?
|
|
119
|
-
vocabulary = json_body["vocabulary"]
|
|
112
|
+
vocabulary = merge_vocabulary(json_body["vocabulary"])
|
|
120
113
|
result = call_stt(audio_b64, mime, vocabulary)
|
|
121
114
|
|
|
122
115
|
if result["success"]
|
|
123
116
|
text = result["text"].to_s.strip
|
|
124
|
-
|
|
117
|
+
transcript_path = File.join(dir, "transcript.jsonl")
|
|
118
|
+
if !text.empty? && !hallucinated_transcript?(text, vocabulary, transcript_path)
|
|
125
119
|
entry = { ts: Time.now.utc.iso8601, text: text }
|
|
126
|
-
File.open(
|
|
120
|
+
File.open(transcript_path, "a") { |f| f.puts(JSON.generate(entry)) }
|
|
121
|
+
json(text: text)
|
|
122
|
+
else
|
|
123
|
+
json(text: "")
|
|
127
124
|
end
|
|
128
|
-
json(text: text)
|
|
129
125
|
else
|
|
130
126
|
error!(result["error"] || "STT failed", status: 502)
|
|
131
127
|
end
|
|
@@ -138,12 +134,22 @@ class MeetingExt < Clacky::ApiExtension
|
|
|
138
134
|
# Submits a question to the session agent with recent transcript as context.
|
|
139
135
|
post "/ask" do
|
|
140
136
|
sid, mid = json_body.values_at("session_id", "meeting_id")
|
|
141
|
-
question = json_body["question"]
|
|
142
|
-
error!("session_id, meeting_id, question required", status: 422) unless sid && mid && question
|
|
137
|
+
question = json_body["question"].to_s.strip
|
|
138
|
+
error!("session_id, meeting_id, question required", status: 422) unless sid && mid && !question.empty?
|
|
143
139
|
|
|
144
140
|
dir = meeting_dir(sid, mid)
|
|
145
141
|
error!("meeting not found", status: 404) unless File.directory?(dir)
|
|
146
142
|
|
|
143
|
+
meaningful = question.gsub(/[^\p{L}\p{N}]/, "")
|
|
144
|
+
if meaningful.length < 4
|
|
145
|
+
logger.warn("ask: question too short, dropping (question=#{question.inspect})")
|
|
146
|
+
next json(ok: false, dropped: true, reason: "question_too_short")
|
|
147
|
+
end
|
|
148
|
+
if question.match?(/\A(open)?clacky\z/i) || question.match?(/\A小?克\z/) || question.match?(/\A克拉奇\z/)
|
|
149
|
+
logger.warn("ask: question is a bare wake/brand word, dropping (question=#{question.inspect})")
|
|
150
|
+
next json(ok: false, dropped: true, reason: "question_is_brand_word")
|
|
151
|
+
end
|
|
152
|
+
|
|
147
153
|
context = recent_transcript(dir, minutes: 5)
|
|
148
154
|
|
|
149
155
|
prompt = <<~PROMPT
|
|
@@ -157,7 +163,7 @@ class MeetingExt < Clacky::ApiExtension
|
|
|
157
163
|
#{question}
|
|
158
164
|
PROMPT
|
|
159
165
|
|
|
160
|
-
submit_task(sid, prompt, display_message: "🎤 #{question}")
|
|
166
|
+
submit_task(sid, prompt, display_message: "🎤 #{question}", interrupt: true)
|
|
161
167
|
json(ok: true)
|
|
162
168
|
end
|
|
163
169
|
|
|
@@ -286,6 +292,52 @@ class MeetingExt < Clacky::ApiExtension
|
|
|
286
292
|
saved.empty? ? "" : saved
|
|
287
293
|
end
|
|
288
294
|
|
|
295
|
+
private def merge_vocabulary(user_terms)
|
|
296
|
+
parts = SYSTEM_VOCABULARY.split(/\s*,\s*/) + user_terms.to_s.split(/\s*,\s*/)
|
|
297
|
+
parts.map(&:strip).reject(&:empty?).uniq.join(", ")
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
HALLUCINATION_PHRASES = Set.new(%w[
|
|
301
|
+
no no. yes yes. ok okay you bye . .. ...
|
|
302
|
+
thanks thank\ you
|
|
303
|
+
uh um hmm mm mm-hmm yeah yeah. yep but and so oh ah ahh huh
|
|
304
|
+
an a i the more well right hi hey wow
|
|
305
|
+
嗯 啊 哦 呃 谢谢 谢谢观看 谢谢大家 好 好的 对
|
|
306
|
+
]).freeze
|
|
307
|
+
|
|
308
|
+
DEDUP_WINDOW_SECONDS = 3.0
|
|
309
|
+
|
|
310
|
+
private def hallucinated_transcript?(text, vocabulary, transcript_path)
|
|
311
|
+
normalized = normalize_transcript(text)
|
|
312
|
+
return true if normalized.empty?
|
|
313
|
+
return true if HALLUCINATION_PHRASES.include?(normalized)
|
|
314
|
+
return true if only_vocabulary_term?(normalized, vocabulary)
|
|
315
|
+
return true if recent_duplicate?(normalized, transcript_path)
|
|
316
|
+
false
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
private def normalize_transcript(text)
|
|
320
|
+
text.to_s.downcase.gsub(/[[:space:][:punct:]]+/, " ").strip
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
private def only_vocabulary_term?(normalized, vocabulary)
|
|
324
|
+
terms = vocabulary.to_s.split(/\s*,\s*/).map { |t| normalize_transcript(t) }.reject(&:empty?)
|
|
325
|
+
terms.include?(normalized)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
private def recent_duplicate?(normalized, transcript_path)
|
|
329
|
+
return false unless File.exist?(transcript_path)
|
|
330
|
+
|
|
331
|
+
cutoff = Time.now.utc - DEDUP_WINDOW_SECONDS
|
|
332
|
+
File.foreach(transcript_path).to_a.last(5).any? do |line|
|
|
333
|
+
entry = JSON.parse(line) rescue nil
|
|
334
|
+
next false unless entry
|
|
335
|
+
ts = Time.parse(entry["ts"].to_s) rescue nil
|
|
336
|
+
next false unless ts && ts >= cutoff
|
|
337
|
+
normalize_transcript(entry["text"]) == normalized
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
289
341
|
private def recent_transcript(dir, minutes: 5)
|
|
290
342
|
path = File.join(dir, "transcript.jsonl")
|
|
291
343
|
return "" unless File.exist?(path)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
id: meeting
|
|
2
|
+
name: Meeting
|
|
3
|
+
description: Real-time meeting transcription and AI assistant
|
|
4
|
+
version: "0.1.0"
|
|
5
|
+
author: OpenClacky
|
|
6
|
+
homepage: https://www.openclacky.com
|
|
7
|
+
license: MIT
|
|
8
|
+
origin: self
|
|
9
|
+
contributes:
|
|
10
|
+
api: api/handler.rb
|
|
11
|
+
panels:
|
|
12
|
+
- id: meeting
|
|
13
|
+
view: panels/meeting/view.js
|
|
14
|
+
skills:
|
|
15
|
+
- id: meeting-summarizer
|