aimeat 1.34.0 → 1.36.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/dist/.env.example +25 -0
- package/dist/locales/en.json +58 -6
- package/dist/locales/fi.json +58 -6
- package/dist/public/css/views/secretary.css +52 -0
- package/dist/public/js/i18n.js +9 -0
- package/dist/public/js/services/secretary-helpers.js +8 -4
- package/dist/public/js/services/secretary-policy.js +28 -3
- package/dist/public/spa.html +5 -1
- package/dist/public/views/my-company.js +13 -1
- package/dist/public/views/profile/agents/agent-card.js +6 -4
- package/dist/public/views/profile/agents/tab-integration.js +5 -2
- package/dist/public/views/secretary/cards.js +46 -15
- package/dist/public/views/secretary/dashboard.js +68 -2
- package/dist/public/views/secretary.js +221 -40
- package/dist/scripts/archive-stale-versions.d.ts +33 -0
- package/dist/scripts/archive-stale-versions.d.ts.map +1 -0
- package/dist/scripts/archive-stale-versions.js +278 -0
- package/dist/scripts/archive-stale-versions.js.map +1 -0
- package/dist/src/cli/connect/mcp/tools/agent-management.d.ts +4 -1
- package/dist/src/cli/connect/mcp/tools/agent-management.d.ts.map +1 -1
- package/dist/src/cli/connect/mcp/tools/agent-management.js +1 -1
- package/dist/src/cli/connect/mcp/tools/agent-management.js.map +1 -1
- package/dist/src/cli/connect/mcp/tools/organisms.d.ts +2 -0
- package/dist/src/cli/connect/mcp/tools/organisms.d.ts.map +1 -1
- package/dist/src/cli/connect/mcp/tools/organisms.js +9 -1
- package/dist/src/cli/connect/mcp/tools/organisms.js.map +1 -1
- package/dist/src/cli/connect/onboarding-prompt.d.ts +3 -0
- package/dist/src/cli/connect/onboarding-prompt.d.ts.map +1 -1
- package/dist/src/cli/connect/onboarding-prompt.js +10 -3
- package/dist/src/cli/connect/onboarding-prompt.js.map +1 -1
- package/dist/src/config.d.ts +11 -0
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +12 -0
- package/dist/src/config.js.map +1 -1
- package/dist/src/generated/api-types.d.ts +748 -4
- package/dist/src/generated/api-types.d.ts.map +1 -1
- package/dist/src/generated/prisma-postgres/edge.js +4 -3
- package/dist/src/generated/prisma-postgres/index-browser.js +1 -0
- package/dist/src/generated/prisma-postgres/index.d.ts +24 -1
- package/dist/src/generated/prisma-postgres/index.js +4 -3
- package/dist/src/generated/prisma-postgres/package.json +1 -1
- package/dist/src/generated/prisma-postgres/schema.prisma +1 -0
- package/dist/src/generated/prisma-postgres/wasm.js +4 -3
- package/dist/src/mcp/agent-onboarding.d.ts +4 -0
- package/dist/src/mcp/agent-onboarding.d.ts.map +1 -1
- package/dist/src/mcp/agent-onboarding.js +28 -2
- package/dist/src/mcp/agent-onboarding.js.map +1 -1
- package/dist/src/mcp/workspaces.d.ts +4 -0
- package/dist/src/mcp/workspaces.d.ts.map +1 -1
- package/dist/src/mcp/workspaces.js +16 -3
- package/dist/src/mcp/workspaces.js.map +1 -1
- package/dist/src/models/agent-onboarding-schemas.d.ts +47 -0
- package/dist/src/models/agent-onboarding-schemas.d.ts.map +1 -1
- package/dist/src/models/agent-onboarding-schemas.js +117 -0
- package/dist/src/models/agent-onboarding-schemas.js.map +1 -1
- package/dist/src/routes/agent-onboarding.d.ts +5 -0
- package/dist/src/routes/agent-onboarding.d.ts.map +1 -1
- package/dist/src/routes/agent-onboarding.js +21 -2
- package/dist/src/routes/agent-onboarding.js.map +1 -1
- package/dist/src/routes/agents.d.ts +7 -0
- package/dist/src/routes/agents.d.ts.map +1 -1
- package/dist/src/routes/agents.js +48 -5
- package/dist/src/routes/agents.js.map +1 -1
- package/dist/src/routes/libs.d.ts +14 -0
- package/dist/src/routes/libs.d.ts.map +1 -1
- package/dist/src/routes/libs.js +56 -17
- package/dist/src/routes/libs.js.map +1 -1
- package/dist/src/routes/oauth-login.d.ts +26 -21
- package/dist/src/routes/oauth-login.d.ts.map +1 -1
- package/dist/src/routes/oauth-login.js +257 -229
- package/dist/src/routes/oauth-login.js.map +1 -1
- package/dist/src/routes/openrouter.d.ts +3 -0
- package/dist/src/routes/openrouter.d.ts.map +1 -1
- package/dist/src/routes/openrouter.js +10 -5
- package/dist/src/routes/openrouter.js.map +1 -1
- package/dist/src/routes/organisms.d.ts +4 -0
- package/dist/src/routes/organisms.d.ts.map +1 -1
- package/dist/src/routes/organisms.js +32 -5
- package/dist/src/routes/organisms.js.map +1 -1
- package/dist/src/routes/secretary.d.ts +7 -2
- package/dist/src/routes/secretary.d.ts.map +1 -1
- package/dist/src/routes/secretary.js +119 -28
- package/dist/src/routes/secretary.js.map +1 -1
- package/dist/src/routes/site.d.ts +2 -0
- package/dist/src/routes/site.d.ts.map +1 -1
- package/dist/src/routes/site.js +10 -2
- package/dist/src/routes/site.js.map +1 -1
- package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
- package/dist/src/server-bootstrap/routes-loader.js +8 -15
- package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
- package/dist/src/services/ai-completion.d.ts +2 -0
- package/dist/src/services/ai-completion.d.ts.map +1 -1
- package/dist/src/services/ai-completion.js +2 -1
- package/dist/src/services/ai-completion.js.map +1 -1
- package/dist/src/services/config-schema.d.ts.map +1 -1
- package/dist/src/services/config-schema.js +2 -0
- package/dist/src/services/config-schema.js.map +1 -1
- package/dist/src/services/generator-autopilot.d.ts +2 -0
- package/dist/src/services/generator-autopilot.d.ts.map +1 -1
- package/dist/src/services/generator-autopilot.js +3 -1
- package/dist/src/services/generator-autopilot.js.map +1 -1
- package/dist/src/services/notebook-ai.d.ts +2 -0
- package/dist/src/services/notebook-ai.d.ts.map +1 -1
- package/dist/src/services/notebook-ai.js +1 -1
- package/dist/src/services/notebook-ai.js.map +1 -1
- package/dist/src/services/oidc-providers.d.ts +94 -0
- package/dist/src/services/oidc-providers.d.ts.map +1 -0
- package/dist/src/services/oidc-providers.js +181 -0
- package/dist/src/services/oidc-providers.js.map +1 -0
- package/dist/src/services/onboarding-guide.d.ts +53 -0
- package/dist/src/services/onboarding-guide.d.ts.map +1 -0
- package/dist/src/services/onboarding-guide.js +81 -0
- package/dist/src/services/onboarding-guide.js.map +1 -0
- package/dist/src/services/organism-template.d.ts.map +1 -1
- package/dist/src/services/organism-template.js +3 -1
- package/dist/src/services/organism-template.js.map +1 -1
- package/dist/src/services/scheduler.d.ts +9 -0
- package/dist/src/services/scheduler.d.ts.map +1 -1
- package/dist/src/services/scheduler.js +53 -24
- package/dist/src/services/scheduler.js.map +1 -1
- package/dist/src/services/secretary-workbrief.d.ts +144 -0
- package/dist/src/services/secretary-workbrief.d.ts.map +1 -0
- package/dist/src/services/secretary-workbrief.js +315 -0
- package/dist/src/services/secretary-workbrief.js.map +1 -0
- package/dist/src/services/secretary-worktier.d.ts +118 -0
- package/dist/src/services/secretary-worktier.d.ts.map +1 -0
- package/dist/src/services/secretary-worktier.js +157 -0
- package/dist/src/services/secretary-worktier.js.map +1 -0
- package/dist/src/services/skill-bundle/generator.d.ts +5 -0
- package/dist/src/services/skill-bundle/generator.d.ts.map +1 -1
- package/dist/src/services/skill-bundle/generator.js +22 -1
- package/dist/src/services/skill-bundle/generator.js.map +1 -1
- package/dist/src/storage/interface.d.ts +2 -1
- package/dist/src/storage/interface.d.ts.map +1 -1
- package/dist/src/storage/interface.js.map +1 -1
- package/dist/src/storage/providers/mongodb/index.d.ts +1 -0
- package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
- package/dist/src/storage/providers/mongodb/index.js +27 -0
- package/dist/src/storage/providers/mongodb/index.js.map +1 -1
- package/dist/src/storage/providers/sqlite/index.d.ts +1 -0
- package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/index.js +18 -4
- package/dist/src/storage/providers/sqlite/index.js.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.js +6 -0
- package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
- package/dist/src/storage/repositories/identity.repository.d.ts +2 -0
- package/dist/src/storage/repositories/identity.repository.d.ts.map +1 -1
- package/dist/src/utils/env-config.d.ts.map +1 -1
- package/dist/src/utils/env-config.js +11 -0
- package/dist/src/utils/env-config.js.map +1 -1
- package/dist/src/utils/node-descriptor.d.ts.map +1 -1
- package/dist/src/utils/node-descriptor.js +1 -0
- package/dist/src/utils/node-descriptor.js.map +1 -1
- package/package.json +3 -1
- package/prisma/schema.postgres.prisma +1 -0
- package/prisma/schema.prisma +1 -0
|
@@ -30,7 +30,7 @@ const html = htm.bind(h);
|
|
|
30
30
|
import { t } from '/js/i18n.js';
|
|
31
31
|
import { CopyButton } from '/components/CopyButton.js';
|
|
32
32
|
import { Markdown } from '/components/Markdown.js';
|
|
33
|
-
import { SECRETARY_CAPABILITIES, BANDS } from '/js/services/secretary-policy.js';
|
|
33
|
+
import { SECRETARY_CAPABILITIES, BANDS, autonomyLevelOf } from '/js/services/secretary-policy.js';
|
|
34
34
|
import { buildInterviewPrompt, groupFeedByDay } from '/js/services/secretary-helpers.js';
|
|
35
35
|
|
|
36
36
|
/** Context "hats" switcher chips + add. */
|
|
@@ -227,9 +227,22 @@ export function brainCard(p) {
|
|
|
227
227
|
/** Operating model: stop-spending + daily budget + per-capability autonomy bands. */
|
|
228
228
|
export function operatingCard(p) {
|
|
229
229
|
const policy = p.policy;
|
|
230
|
+
const level = autonomyLevelOf(policy.bands); // 'suggest' | 'assist' — the single toggle's state
|
|
230
231
|
return html`
|
|
231
232
|
<section class="sec-card">
|
|
232
233
|
<h2 class="sec-h2">${t('secretary.howIWork')}</h2>
|
|
234
|
+
|
|
235
|
+
${/* ONE autonomy toggle (collapses the per-capability band sprawl). Heavy work is always a Work
|
|
236
|
+
Brief a Builder executes — never something the cheap Secretary does on its own. */ ''}
|
|
237
|
+
<div class="sec-autonomy">
|
|
238
|
+
${[['suggest', 'secretary.autonomy.suggest', 'secretary.autonomy.suggestHint'],
|
|
239
|
+
['assist', 'secretary.autonomy.assist', 'secretary.autonomy.assistHint']].map(([val, label, hint]) => html`
|
|
240
|
+
<label class=${'sec-auto-opt' + (level === val ? ' sec-auto-opt--on' : '')} key=${val}>
|
|
241
|
+
<input type="radio" name="sec-autonomy" checked=${level === val} onChange=${() => p.setAutonomy(val)} />
|
|
242
|
+
<span><strong>${t(label)}</strong><br/><span class="sec-hint">${t(hint)}</span></span>
|
|
243
|
+
</label>`)}
|
|
244
|
+
</div>
|
|
245
|
+
|
|
233
246
|
<label class="sec-stop ${policy.stopSpending ? 'on' : ''}">
|
|
234
247
|
<input type="checkbox" checked=${policy.stopSpending} onChange=${p.toggleStop} />
|
|
235
248
|
<span><strong>${t('secretary.stopSpending')}</strong><br/><span class="sec-hint">${t('secretary.stopSpendingHint')}</span></span>
|
|
@@ -239,18 +252,24 @@ export function operatingCard(p) {
|
|
|
239
252
|
<input type="number" min="0" class="sec-budget-in" value=${policy.dailyMorselBudget == null ? '' : policy.dailyMorselBudget} placeholder=${t('secretary.noLimit')} onChange=${(e) => p.setBudget(e.target.value)} />
|
|
240
253
|
<span class="sec-hint">${t('secretary.dailyBudgetHint')}</span>
|
|
241
254
|
</div>
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
255
|
+
|
|
256
|
+
${/* Per-capability bands are still here for power users, tucked behind an Advanced disclosure so the
|
|
257
|
+
default surface is just the one toggle above. */ ''}
|
|
258
|
+
<details class="sec-adv">
|
|
259
|
+
<summary>${t('secretary.advancedBands')}</summary>
|
|
260
|
+
<p class="sec-hint sec-bands-note">${t('secretary.bandsNote')}</p>
|
|
261
|
+
<ul class="sec-caps">
|
|
262
|
+
${SECRETARY_CAPABILITIES.map((c) => html`
|
|
263
|
+
<li class="sec-cap" key=${c.id}>
|
|
264
|
+
<span class="sec-cap-label">${t('secretary.cap.' + c.id)}${c.costs ? html`<span class="sec-cap-tag">${t('secretary.capCosts')}</span>` : null}</span>
|
|
265
|
+
${c.enterprise
|
|
266
|
+
? html`<span class="sec-cap-locked sec-cap-ent">${t('secretary.enterprise')}</span>`
|
|
267
|
+
: c.locked
|
|
268
|
+
? html`<span class="sec-cap-locked">${t('secretary.band.' + policy.bands[c.id])} · ${t('secretary.locked')}</span>`
|
|
269
|
+
: html`<select class="sec-band" value=${policy.bands[c.id]} onChange=${(e) => p.setBand(c.id, e.target.value)}>${BANDS.map((b) => html`<option value=${b} selected=${policy.bands[c.id] === b}>${t('secretary.band.' + b)}</option>`)}</select>`}
|
|
270
|
+
</li>`)}
|
|
271
|
+
</ul>
|
|
272
|
+
</details>
|
|
254
273
|
</section>`;
|
|
255
274
|
}
|
|
256
275
|
|
|
@@ -278,13 +297,25 @@ function routineStepRow(p, r, s) {
|
|
|
278
297
|
const isDelegate = s.capability === 'delegate';
|
|
279
298
|
const agents = p.agents || [];
|
|
280
299
|
const workflows = p.workflows || [];
|
|
300
|
+
// Unblur the delegation target by ROLE: node-local Specialists vs connected big-AI Builders. The Secretary
|
|
301
|
+
// (the watcher) is never a delegation target. Same handler/value (agent name) — the optgroups just label
|
|
302
|
+
// which kind of agent each is, so "pick a specialist" no longer lumps Builders + specialists together.
|
|
303
|
+
const isSpecialist = (a) => (a.tags || []).includes('system:specialist');
|
|
304
|
+
const isSecretary = (a) => (a.tags || []).includes('system:secretary') || a.name === 'secretary';
|
|
305
|
+
const specialistAgents = agents.filter((a) => isSpecialist(a));
|
|
306
|
+
const builderAgents = agents.filter((a) => !isSpecialist(a) && !isSecretary(a));
|
|
281
307
|
// B4: delegate to one agent (agent task) · G7: OR run an Agent Workflow chaining specialists.
|
|
282
308
|
const delegateControl = html`
|
|
283
309
|
<div class="sec-delegate">
|
|
284
|
-
${
|
|
310
|
+
${(specialistAgents.length + builderAgents.length) > 0 ? html`
|
|
285
311
|
<select class="sec-band" value=${p.delegateAgent} onChange=${(e) => p.setDelegateAgent(e.target.value)}>
|
|
286
312
|
<option value="" selected=${!p.delegateAgent}>${t('secretary.next.delegatePick')}</option>
|
|
287
|
-
${
|
|
313
|
+
${specialistAgents.length ? html`<optgroup label=${t('secretary.next.delegateSpecialists')}>
|
|
314
|
+
${specialistAgents.map((a) => html`<option value=${a.name} selected=${p.delegateAgent === a.name}>${(a.name)}</option>`)}
|
|
315
|
+
</optgroup>` : null}
|
|
316
|
+
${builderAgents.length ? html`<optgroup label=${t('secretary.next.delegateBuilders')}>
|
|
317
|
+
${builderAgents.map((a) => html`<option value=${a.name} selected=${p.delegateAgent === a.name}>${(a.name)}</option>`)}
|
|
318
|
+
</optgroup>` : null}
|
|
288
319
|
</select>
|
|
289
320
|
<button class="btn-primary btn-sm" disabled=${busy || !p.delegateAgent} onClick=${() => p.approveStep(r, s, { agentName: p.delegateAgent })}>${busy ? t('secretary.next.running') : t('secretary.next.delegateGo')}</button>` : null}
|
|
290
321
|
${workflows.length > 0 ? html`
|
|
@@ -7,9 +7,13 @@
|
|
|
7
7
|
* collapsible "Manage & setup" disclosure header that tucks the set-up-once config cards away.
|
|
8
8
|
* The view (views/secretary.js) owns all state/handlers and passes them in; the config cards
|
|
9
9
|
* themselves still live in ./cards.js / ./cards-reach.js. Redesign: docs/internal/2026-06-25-secretary-view-redesign.md.
|
|
10
|
-
* @structure quickActionRow · dashStatus · standPanel · actionItemsCard · routinesCard · quickActionsManager · manageHeader (one render function each)
|
|
11
|
-
* @usage import { quickActionRow, dashStatus, standPanel, actionItemsCard, routinesCard, quickActionsManager, manageHeader } from '/views/secretary/dashboard.js';
|
|
10
|
+
* @structure quickActionRow · dashStatus · standPanel · briefTrayCard · actionItemsCard · routinesCard · quickActionsManager · manageHeader (one render function each)
|
|
11
|
+
* @usage import { quickActionRow, dashStatus, standPanel, briefTrayCard, actionItemsCard, routinesCard, quickActionsManager, manageHeader } from '/views/secretary/dashboard.js';
|
|
12
12
|
* @version-history
|
|
13
|
+
* v0.10.0 — 2026-07-01 — Light-vs-heavy seam: briefTrayCard — the Work Brief tray. The cheap Secretary
|
|
14
|
+
* detects structural gaps and proposes grounded briefs; this renders each with "Open in Claude Desktop"
|
|
15
|
+
* (CopyButton with an MCP preface + the brief) and "Send to a connected Builder" (lane b dispatch), so
|
|
16
|
+
* heavy work goes to a big AI via MCP and never gets cheap-authored here.
|
|
13
17
|
* v0.9.0 — 2026-06-28 — strategyCard: optional steering frame (vision/mission/principles/risks +
|
|
14
18
|
* current→target + ordered milestone gates with status/focus/reorder/goal-linking + AI re-plan).
|
|
15
19
|
* v0.8.0 — 2026-06-28 — secretaryDangerCard: a guarded "Reset Secretary" control (type RESET to wipe
|
|
@@ -33,6 +37,7 @@ import htm from 'htm';
|
|
|
33
37
|
const html = htm.bind(h);
|
|
34
38
|
import { t } from '/js/i18n.js';
|
|
35
39
|
import { Markdown } from '/components/Markdown.js';
|
|
40
|
+
import { CopyButton } from '/components/CopyButton.js';
|
|
36
41
|
|
|
37
42
|
/** Core (+ later dynamic) quick actions: a button row above the chat. `items` are descriptors
|
|
38
43
|
* `{ key, label, title?, primary?, disabled?, hidden?, onClick }` so B3 can extend with dynamic actions. */
|
|
@@ -106,6 +111,67 @@ export function standPanel(p) {
|
|
|
106
111
|
</section>`;
|
|
107
112
|
}
|
|
108
113
|
|
|
114
|
+
/** Compose the paste a Builder receives in Claude Desktop: a connect-first preface + the full brief.
|
|
115
|
+
* The preface tells the assistant to connect via the appdev MCP if it isn't already, so pasting into a
|
|
116
|
+
* Claude Desktop that has NOT been wired to the node still leads somewhere instead of silently failing. */
|
|
117
|
+
function heavyWorkPrompt(b) {
|
|
118
|
+
return `I'd like you to carry out a work brief on my AIMEAT node using the appdev MCP tools (the aimeat_* tools).
|
|
119
|
+
|
|
120
|
+
FIRST — make sure you can reach my node: if you are not already connected to my AIMEAT node via the appdev MCP, connect now. In the AIMEAT app open Profile → Agents → "Connect a New Agent" for the exact device-authorization steps (or read the node's /llms.txt). You need the aimeat_* tools available before you can do any of the work below.
|
|
121
|
+
|
|
122
|
+
THEN — carry out the brief end-to-end with those tools: build/fill via aimeat_workspace_* / aimeat_organism_*, ground everything in the real sources listed, and never invent facts. If a records space has no real data, leave it empty rather than make something up.
|
|
123
|
+
|
|
124
|
+
${b.brief || ''}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** One Work Brief row: objective + ws + a markdown preview + the two heavy-work lanes (Open in Claude
|
|
128
|
+
* Desktop / Send to a connected Builder) + Done. The seam made physical — the cheap Secretary proposed
|
|
129
|
+
* this; a Builder (a big AI via MCP) executes it. */
|
|
130
|
+
function briefRow(b, p) {
|
|
131
|
+
const builders = p.builders || [];
|
|
132
|
+
const dispatched = b.status === 'dispatched';
|
|
133
|
+
return html`
|
|
134
|
+
<div class="sec-brief" key=${b.id}>
|
|
135
|
+
<div class="sec-brief-top">
|
|
136
|
+
<span class="sec-brief-badge ${dispatched ? 'is-dispatched' : ''}">${dispatched ? t('secretary.brief.dispatchedBadge') : t('secretary.brief.heavyBadge')}</span>
|
|
137
|
+
<span class="sec-brief-obj">${b.objective}</span>
|
|
138
|
+
${b.wsName ? html`<span class="sec-hint sec-brief-ws">${b.wsName}</span>` : null}
|
|
139
|
+
</div>
|
|
140
|
+
<details class="sec-brief-detail">
|
|
141
|
+
<summary>${t('secretary.brief.preview')}</summary>
|
|
142
|
+
<div class="sec-brief-md"><${Markdown} text=${b.brief || ''} /></div>
|
|
143
|
+
</details>
|
|
144
|
+
${dispatched ? html`<div class="sec-hint sec-brief-wait">${t('secretary.brief.dispatchedNote')}</div>` : null}
|
|
145
|
+
<div class="sec-brief-actions">
|
|
146
|
+
<${CopyButton} text=${heavyWorkPrompt(b)} className="btn-primary btn-sm" label=${t('secretary.brief.openClaude')} />
|
|
147
|
+
${builders.length ? html`
|
|
148
|
+
<select class="sec-select" disabled=${dispatched || p.dispatching === b.id}
|
|
149
|
+
onChange=${(e) => { const v = e.target.value; e.target.value = ''; if (v) p.onDispatch(b.id, v); }}>
|
|
150
|
+
<option value="">${dispatched ? t('secretary.brief.waiting') : t('secretary.brief.sendBuilder')}</option>
|
|
151
|
+
${builders.map((a) => html`<option value=${a.gaii} key=${a.gaii}>${a.name}</option>`)}
|
|
152
|
+
</select>` : html`<span class="sec-hint">${t('secretary.brief.noBuilder')}</span>`}
|
|
153
|
+
<button class="btn-ghost btn-sm" onClick=${() => p.onResolve(b.id)}>${t('secretary.brief.done')}</button>
|
|
154
|
+
</div>
|
|
155
|
+
</div>`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** The Work Brief tray: heavy work the Secretary detected and prepared for a Builder. Null when empty
|
|
159
|
+
* (no gaps → nothing to hand off). The clear divide between "watch + propose" (cheap, here) and
|
|
160
|
+
* "build + author" (a big AI via MCP). */
|
|
161
|
+
export function briefTrayCard(p) {
|
|
162
|
+
const briefs = p.briefs || [];
|
|
163
|
+
if (!briefs.length) return null;
|
|
164
|
+
return html`
|
|
165
|
+
<section class="sec-card sec-brief-tray">
|
|
166
|
+
<div class="sec-card-head">
|
|
167
|
+
<h2 class="sec-h2">${t('secretary.brief.startHere')} ${t('secretary.brief.title')} <span class="sec-count">${briefs.length}</span></h2>
|
|
168
|
+
<span class="sec-hint">${t('secretary.brief.subtitle')}</span>
|
|
169
|
+
</div>
|
|
170
|
+
<p class="sec-brief-banner">${t('secretary.brief.mcpNote')} <a class="sec-linkbtn" href="/v1/profile">${t('secretary.brief.connectLink')}</a></p>
|
|
171
|
+
${briefs.map((b) => briefRow(b, p))}
|
|
172
|
+
</section>`;
|
|
173
|
+
}
|
|
174
|
+
|
|
109
175
|
/** "What's next" — the Secretary's proposed next actions, each with Do it / Skip; doing one runs it. */
|
|
110
176
|
export function whatsNextPanel(p) {
|
|
111
177
|
const n = p.answer;
|
|
@@ -11,6 +11,34 @@
|
|
|
11
11
|
* @structure SECRETARY_ICON · SecretaryView (default) — state, effects, handlers, layout (cards in ./secretary/cards.js + ./secretary/cards-reach.js + dashboard chrome in ./secretary/dashboard.js)
|
|
12
12
|
* @usage routed at /v1/secretary by spa.html (+ portal.ts spaRoutes).
|
|
13
13
|
* @version-history
|
|
14
|
+
* v0.26.0 — 2026-07-01 — Opt-in feature: the Secretary is now gated on AIMEAT_SECRETARY_ENABLED (off by
|
|
15
|
+
* default). The header nav link is hidden and this view redirects to /v1/profile when the node has it
|
|
16
|
+
* disabled (flag read from /v1/site/header-nav `features.secretary`).
|
|
17
|
+
* v0.25.0 — 2026-07-01 — Light-vs-heavy seam: setup no longer cheap-authors content (that hallucinated).
|
|
18
|
+
* applyResult now CREATES the workspace structure and then proposes grounded Work Briefs (POST
|
|
19
|
+
* /v1/secretary/brief) the user hands to a Builder (a connected big AI via the appdev MCP tools). Drops
|
|
20
|
+
* the per-workspace author-now fill loop + the author_content band. The dashboard brief tray (cards)
|
|
21
|
+
* renders the briefs with "Open in Claude Desktop" / "Send to a connected Builder".
|
|
22
|
+
* v0.24.0 — 2026-06-30 — Never-empty + honest fill result. normalizeInlineManifest now GUARANTEES every
|
|
23
|
+
* workspace has a document space (appends a "Muistiinpanot" doc space when the design gave only records
|
|
24
|
+
* spaces) so the setup fill always has a thinking-home, and is genuinely document-first for unspecified
|
|
25
|
+
* spaces. The per-workspace fill loop counts a workspace as filled ONLY on a real 🖊️ authored line (not
|
|
26
|
+
* a ⚠️ summary), retries the HTTP call once (flaky owl-alpha), and the toast names workspaces it
|
|
27
|
+
* couldn't fill instead of a bare count.
|
|
28
|
+
* v0.23.0 — 2026-06-30 — Autonomy chosen up front: "Set up my Secretary" first asks HOW autonomous it
|
|
29
|
+
* should be (Prepare/Draft · Run/Act · Only-when-I-ask/Off), each explained in plain words incl. the
|
|
30
|
+
* AI-credit cost, BEFORE anything runs — so nobody stumbles into Act bands later. The choice sets the
|
|
31
|
+
* new context's author_content band and, for Draft/Act, turns the daily check-in on. Browser-verified.
|
|
32
|
+
* v0.22.0 — 2026-06-30 — Live setup progress: the fill runs ONE workspace at a time (author-now?ws=)
|
|
33
|
+
* and a top-level overlay (survives the hire→dashboard switch) shows "Filling workspace N/total ·
|
|
34
|
+
* <name>" + a progress bar through the whole slow fill, instead of a couple of fleeting toasts.
|
|
35
|
+
* Browser-verified: overlay cycled 1/2 → 2/2 with the bar advancing.
|
|
36
|
+
* v0.21.0 — 2026-06-30 — Deterministic workspace structure (NO AI): genAndApplyManifest normalizes any
|
|
37
|
+
* design-provided inline manifest into a valid one (fixes namespace form + backfills schemaRef/backing/
|
|
38
|
+
* writeRole/mode, rekeys schemas, derives a records schema when missing) and falls back to a plain
|
|
39
|
+
* document space — instead of a flaky per-workspace AI generator call that left some workspaces as
|
|
40
|
+
* "set up when opened". Every workspace now gets its structure reliably, every time. Verified on the
|
|
41
|
+
* dev server: a 6-workspace design (with non-conforming komento.* namespaces) all came out structured.
|
|
14
42
|
* v0.20.0 — 2026-06-30 — Content at handshake: a fresh hire now fills its workspaces with REAL content
|
|
15
43
|
* immediately (POST /v1/secretary/author-now, long timeout) and drops any left empty; the workspace
|
|
16
44
|
* generator runs with skipExamples so no fake "example-N" placeholder records are written.
|
|
@@ -73,12 +101,12 @@ import { t, getLocale } from '/js/i18n.js';
|
|
|
73
101
|
import { api, apiGet, apiPost, apiPut, apiDelete } from '/js/api.js';
|
|
74
102
|
import { useViewCSS } from '/components/useViewCSS.js';
|
|
75
103
|
import { useToast } from '/components/Toast.js';
|
|
76
|
-
import { createOrganism, createWorkspace,
|
|
77
|
-
import { defaultPolicy, mergePolicy } from '/js/services/secretary-policy.js';
|
|
104
|
+
import { createOrganism, createWorkspace, validateGenerated, applyGeneratedWorkspace } from '/js/services/organisms.js';
|
|
105
|
+
import { defaultPolicy, mergePolicy, autonomyPreset } from '/js/services/secretary-policy.js';
|
|
78
106
|
import { buildDesignPrompt, extractJson, snapshotOf, genCtxId, migrateConfig, suggestContextId, computeBudgetInfo, computeReliability, sanitizeQuickActions, normalizeStrategy, MILESTONE_STATUSES, buildStrategyReplanPrompt, linkMilestoneGoals, keepKnownGoalRefs, SECRETARY_AIMEAT_PRIMER } from '/js/services/secretary-helpers.js';
|
|
79
107
|
import { contextSwitcher, hirePanel, chatCard, findCard, noteCard, decisionsCard, brainCard, operatingCard, historyCard, metaCard, whatsNextCard, feedCard, automationCard, goalsCard, decisionLogCard } from '/views/secretary/cards.js';
|
|
80
108
|
import { createResourceCard, knowledgeCard, accessCard, crewCard } from '/views/secretary/cards-reach.js';
|
|
81
|
-
import { quickActionRow, dashStatus, standPanel, whatsNextPanel, actionItemsCard, routinesCard, triggersCard, quickActionsManager, manageHeader, workflowDesignPanel, secretaryDangerCard, strategyCard } from '/views/secretary/dashboard.js';
|
|
109
|
+
import { quickActionRow, dashStatus, standPanel, briefTrayCard, whatsNextPanel, actionItemsCard, routinesCard, triggersCard, quickActionsManager, manageHeader, workflowDesignPanel, secretaryDangerCard, strategyCard } from '/views/secretary/dashboard.js';
|
|
82
110
|
import { fetchWorkflowOffers, designWorkflow, saveDesignedWorkflow, slugifyWorkflowId } from '/views/secretary/workflow-design.js';
|
|
83
111
|
import { useSpecialists, specialistsCard } from '/views/secretary/use-specialists.js';
|
|
84
112
|
import { useIntake } from '/views/secretary/use-intake.js';
|
|
@@ -106,13 +134,29 @@ export const SECRETARY_ICON = html`
|
|
|
106
134
|
|
|
107
135
|
export default function SecretaryView() {
|
|
108
136
|
useViewCSS('/css/views/secretary.css');
|
|
137
|
+
// Secretary is opt-in per node (AIMEAT_SECRETARY_ENABLED). If it's disabled but this view is reached
|
|
138
|
+
// via a direct URL/bookmark (no nav link is rendered when off), bounce back to the profile.
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
let cancelled = false;
|
|
141
|
+
fetch('/v1/site/header-nav')
|
|
142
|
+
.then(r => r.json())
|
|
143
|
+
.then(d => { if (!cancelled && d?.ok && d.data?.features && d.data.features.secretary === false) window.location.replace('/v1/profile'); })
|
|
144
|
+
.catch(() => {});
|
|
145
|
+
return () => { cancelled = true; };
|
|
146
|
+
}, []);
|
|
109
147
|
const { showToast, ToastContainer } = useToast();
|
|
110
148
|
const [secretary, setSecretary] = useState(undefined); // undefined=loading, null=not provisioned
|
|
111
149
|
const [hasOpenRouterKey, setHasOpenRouterKey] = useState(false); // owner has an OpenRouter key configured
|
|
112
150
|
const [config, setConfig] = useState(null); // { contexts:[...], activeContextId }
|
|
151
|
+
const [briefs, setBriefs] = useState([]); // open Work Briefs (heavy work to hand a Builder)
|
|
152
|
+
const [builders, setBuilders] = useState([]); // connected big-AI agents (not Secretary / specialists)
|
|
153
|
+
const [dispatching, setDispatching] = useState(''); // briefId currently being dispatched
|
|
113
154
|
const [result, setResult] = useState('');
|
|
114
155
|
const [needs, setNeeds] = useState('');
|
|
115
156
|
const [applying, setApplying] = useState(false);
|
|
157
|
+
const [setupStep, setSetupStep] = useState(null); // live setup progress: { phase, done, total, name }
|
|
158
|
+
const [chooseMode, setChooseMode] = useState(false); // showing the "how should it work?" autonomy step
|
|
159
|
+
const [setupMode, setSetupMode] = useState('draft'); // chosen autonomy: 'off' | 'draft' | 'act'
|
|
116
160
|
const [generating, setGenerating] = useState(false);
|
|
117
161
|
const [showHire, setShowHire] = useState(false);
|
|
118
162
|
const [hireMode, setHireMode] = useState('new'); // 'new' (add context) | 'edit' (re-run active)
|
|
@@ -161,17 +205,42 @@ export default function SecretaryView() {
|
|
|
161
205
|
const sec = agents.find((a) => (a.tags || []).includes('system:secretary'))
|
|
162
206
|
|| agents.find((a) => a.name === 'secretary') || null;
|
|
163
207
|
setSecretary(sec);
|
|
208
|
+
// Builders = connected big-AI agents the heavy work is dispatched to — anything that is NOT the
|
|
209
|
+
// Secretary (the watcher) and NOT a node-local specialist. These are visually distinct in the brief tray.
|
|
210
|
+
setBuilders(agents.filter((a) => a && a.name !== 'secretary'
|
|
211
|
+
&& !(a.tags || []).includes('system:secretary') && !(a.tags || []).includes('system:specialist')));
|
|
164
212
|
if (!sec) return;
|
|
165
|
-
const [dir, cfg] = await Promise.all([
|
|
213
|
+
const [dir, cfg, briefResp] = await Promise.all([
|
|
166
214
|
apiGet('/v1/agents/secretary/directives').catch(() => null),
|
|
167
215
|
apiGet('/v1/memory/secretary.config').catch(() => null),
|
|
216
|
+
apiGet('/v1/secretary/briefs').catch(() => null),
|
|
168
217
|
]);
|
|
218
|
+
setBriefs((briefResp && briefResp.data && briefResp.data.briefs) || []);
|
|
169
219
|
const rawCfg = (cfg && cfg.data && cfg.data.value) || null;
|
|
170
220
|
const { config: normalized, changed } = migrateConfig(rawCfg, dir && dir.data);
|
|
171
221
|
if (changed) await apiPost('/v1/memory', { key: 'secretary.config', value: normalized, visibility: 'private' }).catch(() => {});
|
|
172
222
|
setConfig(normalized);
|
|
173
223
|
}, []);
|
|
174
224
|
|
|
225
|
+
// Dispatch a Work Brief to a connected Builder (lane b): queues a task the Builder picks up + runs via
|
|
226
|
+
// the appdev MCP tools. Marks the brief 'dispatched' so the tray reflects "waiting on the Builder".
|
|
227
|
+
const dispatchBrief = useCallback(async (briefId, agentGaii) => {
|
|
228
|
+
if (!briefId || !agentGaii) return;
|
|
229
|
+
setDispatching(briefId);
|
|
230
|
+
try {
|
|
231
|
+
const r = await apiPost(`/v1/secretary/brief/${encodeURIComponent(briefId)}/dispatch`, { targetAgent: agentGaii }).catch(() => null);
|
|
232
|
+
if (r && r.data) { showToast(t('secretary.brief.dispatched')); load(); }
|
|
233
|
+
else showToast(t('secretary.brief.dispatchFailed'), true);
|
|
234
|
+
} finally { setDispatching(''); }
|
|
235
|
+
}, [load, showToast]);
|
|
236
|
+
|
|
237
|
+
// Resolve a Work Brief (the owner handled it themselves, or it's no longer relevant). Marks it 'done'.
|
|
238
|
+
const resolveBrief = useCallback(async (briefId) => {
|
|
239
|
+
if (!briefId) return;
|
|
240
|
+
await apiPost(`/v1/secretary/brief/${encodeURIComponent(briefId)}/done`, {}).catch(() => null);
|
|
241
|
+
load();
|
|
242
|
+
}, [load]);
|
|
243
|
+
|
|
175
244
|
useEffect(() => { load(); }, [load]);
|
|
176
245
|
|
|
177
246
|
// Re-fetch config on server-reported changes: the autonomous tick writes pending decisions + the
|
|
@@ -282,9 +351,14 @@ export default function SecretaryView() {
|
|
|
282
351
|
return ids;
|
|
283
352
|
}, []);
|
|
284
353
|
|
|
285
|
-
//
|
|
286
|
-
//
|
|
287
|
-
//
|
|
354
|
+
// ── Deterministic workspace structure (NO AI) ──
|
|
355
|
+
// The design JSON's inline manifests routinely fail the workspace validator (wrong namespace form, or
|
|
356
|
+
// missing schemaRef/backing/writeRole), which used to push EACH workspace into a flaky per-workspace AI
|
|
357
|
+
// generator call — owl-alpha failing some of those left workspaces as "set up when opened". Instead we
|
|
358
|
+
// NORMALIZE any inline manifest into a valid one ourselves, and fall back to a plain document space.
|
|
359
|
+
// Zero AI calls → every workspace gets its structure reliably, every time.
|
|
360
|
+
const WS_TYPE_MAP = { text: 'string', number: 'number', date: 'string', select: 'string', boolean: 'boolean', reference: 'string' };
|
|
361
|
+
const wsSlug = (s) => String(s || '').trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'tila';
|
|
288
362
|
const backfillEnvelope = (mf, wsId, name) => {
|
|
289
363
|
if (!mf) return;
|
|
290
364
|
mf.manifestVersion = mf.manifestVersion || '1.0';
|
|
@@ -293,25 +367,76 @@ export default function SecretaryView() {
|
|
|
293
367
|
mf.kind = mf.kind || 'project';
|
|
294
368
|
mf.status = ['active', 'paused', 'done', 'archived'].includes(mf.status) ? mf.status : 'active';
|
|
295
369
|
};
|
|
370
|
+
// Make ANY inline manifest valid: backfill the envelope + each objectType's required fields, normalize
|
|
371
|
+
// the namespace to the required shared.* form (rekeying its schema), and derive a schema for a records
|
|
372
|
+
// type that lacks one. Mutates `manifest`; may add keys to `schemas`.
|
|
373
|
+
const normalizeInlineManifest = (manifest, schemas, wsId, name) => {
|
|
374
|
+
backfillEnvelope(manifest, wsId, name);
|
|
375
|
+
const ots = Array.isArray(manifest.objectTypes) ? manifest.objectTypes : [];
|
|
376
|
+
manifest.objectTypes = ots.filter(Boolean).map((ot, i) => {
|
|
377
|
+
const o = { ...ot };
|
|
378
|
+
o.name = String(o.name || ('tila' + (i + 1))).trim() || ('tila' + (i + 1));
|
|
379
|
+
let ns = String(o.namespace || ('shared.' + wsSlug(o.name)));
|
|
380
|
+
if (!/^(meta|shared|member)\./.test(ns)) {
|
|
381
|
+
const tail = ns.includes('.') ? ns.slice(ns.indexOf('.') + 1) : ns;
|
|
382
|
+
const newNs = 'shared.' + (wsSlug(tail) || wsSlug(o.name));
|
|
383
|
+
if (schemas[o.namespace] && !schemas[newNs]) { schemas[newNs] = schemas[o.namespace]; delete schemas[o.namespace]; }
|
|
384
|
+
ns = newNs;
|
|
385
|
+
}
|
|
386
|
+
o.namespace = ns;
|
|
387
|
+
o.backing = o.backing === 'tasks' ? 'tasks' : 'memory';
|
|
388
|
+
o.writeRole = ['owner', 'admin', 'member'].includes(o.writeRole) ? o.writeRole : 'member';
|
|
389
|
+
o.cardinality = o.cardinality === 'one' ? 'one' : 'many';
|
|
390
|
+
o.versioned = o.versioned !== false;
|
|
391
|
+
o.schemaRef = o.schemaRef || ('schema:' + wsSlug(o.name) + '@1');
|
|
392
|
+
// Document-first: keep an explicit mode as the design gave it; for an UNSPECIFIED space default to a
|
|
393
|
+
// DOCUMENT (flexible information — the common case) unless the design clearly describes a record list
|
|
394
|
+
// (real fields, or a schema already supplied for it). A records space is for same-shaped, list-like
|
|
395
|
+
// data (actions/tasks/bugs/a pipeline/a tracker) only.
|
|
396
|
+
if (o.mode !== 'document' && o.mode !== 'records') {
|
|
397
|
+
o.mode = ((Array.isArray(o.fields) && o.fields.length) || schemas[ns]) ? 'records' : 'document';
|
|
398
|
+
}
|
|
399
|
+
if (o.mode !== 'document' && o.backing === 'memory' && !schemas[ns]) {
|
|
400
|
+
const props = {}; (o.fields || []).forEach((f) => { if (f && f.name) props[f.name] = { type: WS_TYPE_MAP[f.type] || 'string' }; });
|
|
401
|
+
if (!props.id) props.id = { type: 'string' };
|
|
402
|
+
schemas[ns] = { type: 'object', properties: props, required: ['id'] };
|
|
403
|
+
}
|
|
404
|
+
return o;
|
|
405
|
+
});
|
|
406
|
+
// Never-empty guarantee: every workspace needs at least one DOCUMENT space — a free-markdown "thinking
|
|
407
|
+
// home" the setup fill can always author grounded content into, even when the design gave only records
|
|
408
|
+
// spaces (records are real-data-only and legitimately start empty). Append a default document space when
|
|
409
|
+
// the design provided none. Additive + idempotent: only when no document space exists.
|
|
410
|
+
if (!manifest.objectTypes.some((o) => o.mode === 'document')) {
|
|
411
|
+
let dns = 'shared.muistiinpanot';
|
|
412
|
+
if (manifest.objectTypes.some((o) => o.namespace === dns)) dns = 'shared.muistiinpanot-' + wsSlug(name);
|
|
413
|
+
manifest.objectTypes.push({ name: 'Muistiinpanot', namespace: dns, backing: 'memory', writeRole: 'member', cardinality: 'many', versioned: true, schemaRef: 'schema:doc@1', mode: 'document' });
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
// A plain document space (free markdown) — the deterministic fallback so a workspace with no usable
|
|
417
|
+
// manifest still gets a valid structure at setup, without any AI call.
|
|
418
|
+
const docSpaceManifest = (wsId, name) => ({
|
|
419
|
+
manifest: { manifestVersion: '1.0', id: wsId, name, kind: 'notes', status: 'active',
|
|
420
|
+
objectTypes: [{ name: 'Dokumentit', namespace: 'shared.dokumentit', backing: 'memory', writeRole: 'member', cardinality: 'many', versioned: true, schemaRef: 'schema:doc@1', mode: 'document' }] },
|
|
421
|
+
schemas: {},
|
|
422
|
+
});
|
|
296
423
|
const genAndApplyManifest = useCallback(async (orgId, wsId, ws) => {
|
|
297
|
-
const name = String(ws.name || '').trim();
|
|
424
|
+
const name = String(ws.name || '').trim() || 'Työtila';
|
|
298
425
|
try {
|
|
299
|
-
// skipExamples: the Secretary fills workspaces with REAL content at setup (author-now),
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
426
|
+
// skipExamples: the Secretary fills workspaces with REAL content at setup (author-now), never fakes.
|
|
427
|
+
if (ws.manifest && typeof ws.manifest === 'object') {
|
|
428
|
+
const schemas = { ...(ws.schemas || {}) };
|
|
429
|
+
const manifest = JSON.parse(JSON.stringify(ws.manifest));
|
|
430
|
+
normalizeInlineManifest(manifest, schemas, wsId, name);
|
|
431
|
+
const provided = { manifest, schemas };
|
|
305
432
|
if (!validateGenerated(provided).length) { await applyGeneratedWorkspace(orgId, wsId, provided, { skipExamples: true }); return; }
|
|
306
433
|
}
|
|
307
|
-
//
|
|
308
|
-
|
|
309
|
-
backfillEnvelope(generated && generated.manifest, wsId, name);
|
|
310
|
-
if (generated && !validateGenerated(generated).length) await applyGeneratedWorkspace(orgId, wsId, generated, { skipExamples: true });
|
|
434
|
+
// No usable inline manifest → a plain document space, applied deterministically (no AI).
|
|
435
|
+
await applyGeneratedWorkspace(orgId, wsId, docSpaceManifest(wsId, name), { skipExamples: true });
|
|
311
436
|
} catch { /* leave it manifest-less; the owner can set it up manually */ }
|
|
312
437
|
}, []);
|
|
313
438
|
|
|
314
|
-
const applyResult = useCallback(async () => {
|
|
439
|
+
const applyResult = useCallback(async (mode = 'off') => {
|
|
315
440
|
setApplying(true);
|
|
316
441
|
try {
|
|
317
442
|
const json = extractJson(result);
|
|
@@ -326,6 +451,8 @@ export default function SecretaryView() {
|
|
|
326
451
|
}
|
|
327
452
|
const newBrain = { purpose: b.purpose, rules };
|
|
328
453
|
const isNew = hireMode === 'new' || !active;
|
|
454
|
+
setSetupStep({ phase: 'structure' }); // live progress: building the space + workspaces
|
|
455
|
+
let fillTargets = []; // [{ id, name }] of new workspaces to fill one at a time
|
|
329
456
|
|
|
330
457
|
let next;
|
|
331
458
|
if (isNew) {
|
|
@@ -347,6 +474,7 @@ export default function SecretaryView() {
|
|
|
347
474
|
}
|
|
348
475
|
// Design + apply each workspace's manifest in parallel so they come out usable, not empty shells.
|
|
349
476
|
await Promise.all(created.map((c) => genAndApplyManifest(orgId, c.wsId, c.ws)));
|
|
477
|
+
fillTargets = created.map((c) => ({ id: c.wsId, name: String((c.ws && c.ws.name) || '').trim() || c.wsId }));
|
|
350
478
|
}
|
|
351
479
|
// B3: seed the brain-proposed quick actions — active on hire (run verbs are dropped by the sanitizer).
|
|
352
480
|
const quickActions = sanitizeQuickActions(json.quickActions, 'brain', 'active');
|
|
@@ -357,7 +485,10 @@ export default function SecretaryView() {
|
|
|
357
485
|
// the owner ticking the linked goals by hand after setup.
|
|
358
486
|
const goalIds = await seedGoals(json.goals, ctxId, org.name);
|
|
359
487
|
const strategy = linkMilestoneGoals(normalizeStrategy(json.strategy), goalIds);
|
|
360
|
-
|
|
488
|
+
// Autonomy chosen in the setup step controls only how active the DAILY tick is (it watches, briefs,
|
|
489
|
+
// and proposes Work Briefs — it never authors content itself, so there is no "fill" band any more).
|
|
490
|
+
const policy = defaultPolicy();
|
|
491
|
+
const ctx = { id: ctxId, name: org.name, brain: newBrain, organismId: orgId || null, organismName: org.name, workspaces: wsSummary, policy, brainHistory: [], quickActions, strategy };
|
|
361
492
|
next = { contexts: [...contexts, ctx], activeContextId: ctx.id };
|
|
362
493
|
} else {
|
|
363
494
|
// Re-run on the active context (reshape): update its brain, snapshot the old one, KEEP its
|
|
@@ -391,31 +522,44 @@ export default function SecretaryView() {
|
|
|
391
522
|
await persistConfig(next);
|
|
392
523
|
await syncDirectives(newBrain);
|
|
393
524
|
window.dispatchEvent(new CustomEvent('aimeat-live-update')); // refresh goals/learning + cards
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
//
|
|
397
|
-
//
|
|
398
|
-
//
|
|
399
|
-
if (isNew) {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
525
|
+
// The Secretary NO LONGER authors content one-shot at handshake (the cheap model hallucinated and went
|
|
526
|
+
// "onto the Builders' turf"). Instead it now proposes grounded Work Briefs for the new (empty)
|
|
527
|
+
// workspaces — heavy work a Builder (a connected big AI via the appdev MCP tools) executes. Fast +
|
|
528
|
+
// deterministic (no slow model call); the dashboard's brief tray then offers "Open in Claude Desktop"
|
|
529
|
+
// / "Send to a connected Builder". Only on a fresh hire, so it never touches existing content.
|
|
530
|
+
if (isNew && fillTargets.length) {
|
|
531
|
+
setSetupStep({ phase: 'fill', done: 0, total: fillTargets.length, name: fillTargets[0].name });
|
|
532
|
+
const r = await api('/v1/secretary/brief', { method: 'POST', body: '{}', timeoutMs: 120000, retries: 1 }).catch(() => null);
|
|
533
|
+
const proposed = (r && r.data && typeof r.data.proposed === 'number') ? r.data.proposed : 0;
|
|
534
|
+
setSetupStep({ phase: 'done', done: fillTargets.length, total: fillTargets.length });
|
|
535
|
+
window.dispatchEvent(new CustomEvent('aimeat-live-update')); // briefs show up in the tray live
|
|
536
|
+
showToast(proposed ? `${t('secretary.briefsReady')} (${proposed})` : t('secretary.hireDone'));
|
|
537
|
+
} else {
|
|
538
|
+
showToast(t('secretary.hireDone'));
|
|
539
|
+
}
|
|
540
|
+
// If the owner chose an autonomous mode, turn the daily check-in ON for them — so it actually keeps
|
|
541
|
+
// working on its own (the band alone does nothing without a schedule). 'off' stays fully manual.
|
|
542
|
+
if (isNew && mode !== 'off') {
|
|
543
|
+
await apiPost('/v1/schedules', { kind: 'secretary', cron: '0 8 * * *', display_name: t('secretary.auto.scheduleName') }).catch(() => {});
|
|
411
544
|
}
|
|
545
|
+
setResult(''); setNeeds(''); setShowHire(false);
|
|
412
546
|
} catch (e) {
|
|
413
547
|
showToast(`${t('secretary.hireError')}: ${e.message}`, true);
|
|
414
548
|
} finally {
|
|
415
|
-
setApplying(false);
|
|
549
|
+
setApplying(false); setSetupStep(null);
|
|
416
550
|
}
|
|
417
551
|
}, [result, hireMode, active, contexts, config, persistConfig, syncDirectives, seedGoals, genAndApplyManifest, showToast]);
|
|
418
552
|
|
|
553
|
+
// "Set up my Secretary" no longer runs immediately — it first asks (one step, up front) HOW autonomous
|
|
554
|
+
// the owner wants it, explained in plain words, so nobody stumbles into Act bands later. The chosen mode
|
|
555
|
+
// is passed to applyResult. Only for a brand-new hire; re-running an existing context keeps its policy.
|
|
556
|
+
const startSetup = useCallback(() => {
|
|
557
|
+
if (!result.trim()) return;
|
|
558
|
+
try { extractJson(result); } catch { showToast(t('secretary.hireBadShape'), true); return; }
|
|
559
|
+
if (hireMode === 'new' || !active) { setSetupMode('draft'); setChooseMode(true); } else { applyResult('off'); }
|
|
560
|
+
}, [result, hireMode, active, applyResult, showToast]);
|
|
561
|
+
const confirmSetup = useCallback(() => { setChooseMode(false); applyResult(setupMode); }, [applyResult, setupMode]);
|
|
562
|
+
|
|
419
563
|
// C2 — direct brain editor: edit the active context's purpose + rules in place (no AI re-run).
|
|
420
564
|
const startBrainEdit = useCallback(() => {
|
|
421
565
|
if (!active) return;
|
|
@@ -811,6 +955,9 @@ ${SECRETARY_AIMEAT_PRIMER}`;
|
|
|
811
955
|
persistConfig({ ...config, contexts: contexts.map((c) => (c.id === active.id ? updated : c)) });
|
|
812
956
|
};
|
|
813
957
|
const setBand = (capId, band) => updateActivePolicy({ bands: { ...policy.bands, [capId]: band } });
|
|
958
|
+
// One autonomy toggle: 'suggest' (surface only) | 'assist' (handle the small stuff). Sets the band preset
|
|
959
|
+
// underneath, so the band machinery + parity tests are unchanged — the toggle is just the simple surface.
|
|
960
|
+
const setAutonomy = (level) => updateActivePolicy({ bands: autonomyPreset(level) });
|
|
814
961
|
const toggleStop = () => updateActivePolicy({ stopSpending: !policy.stopSpending });
|
|
815
962
|
const setBudget = (v) => updateActivePolicy({ dailyMorselBudget: v === '' ? null : Math.max(0, Math.floor(Number(v) || 0)) });
|
|
816
963
|
|
|
@@ -898,7 +1045,7 @@ ${SECRETARY_AIMEAT_PRIMER}`;
|
|
|
898
1045
|
? html`<div class="sec-empty">${hasOpenRouterKey ? t('secretary.provisioning') : t('secretary.notReady')}</div>`
|
|
899
1046
|
: html`
|
|
900
1047
|
${hired ? contextSwitcher({ contexts, activeId, switchContext, openAdd }) : null}
|
|
901
|
-
${showHirePanel ? hirePanel({ firstEver, hireMode, owner, current: hireMode === 'edit' ? active : null, needs, setNeeds, result, setResult, applying, generating, generateInApp, applyResult, onCancel: cancelHire }) : null}
|
|
1048
|
+
${showHirePanel ? hirePanel({ firstEver, hireMode, owner, current: hireMode === 'edit' ? active : null, needs, setNeeds, result, setResult, applying, setupStep, generating, generateInApp, applyResult: startSetup, onCancel: cancelHire }) : null}
|
|
902
1049
|
${hired && !showHire && active ? html`
|
|
903
1050
|
${/* Quick-action row (core verbs; dynamic actions arrive in B3) */ ''}
|
|
904
1051
|
${quickActionRow({ items: [
|
|
@@ -917,6 +1064,9 @@ ${SECRETARY_AIMEAT_PRIMER}`;
|
|
|
917
1064
|
${/* Today — full-width status band, always on top */ ''}
|
|
918
1065
|
${dashStatus({ reliability, budgetInfo, schedule: auto.schedule, lastScan, stale, onReconcile: reconcile, scanning })}
|
|
919
1066
|
|
|
1067
|
+
${/* Heavy work to hand off — the Work Brief tray (the light-vs-heavy seam made physical). */ ''}
|
|
1068
|
+
${briefTrayCard({ briefs, builders, dispatching, onDispatch: dispatchBrief, onResolve: resolveBrief })}
|
|
1069
|
+
|
|
920
1070
|
${/* Customizable two-column dashboard (pin to column / reorder / hide per card; persisted). */ ''}
|
|
921
1071
|
${(() => {
|
|
922
1072
|
const nodes = {
|
|
@@ -966,7 +1116,7 @@ ${SECRETARY_AIMEAT_PRIMER}`;
|
|
|
966
1116
|
crewSummary: crew.agents && crew.agents.length ? `${crew.agents.length} ${t('secretary.dash.crewAgents')}` : '' })}
|
|
967
1117
|
${manageOpen ? html`
|
|
968
1118
|
${brainCard({ brain, active, openEdit, brainDraft, setBrainDraft, startBrainEdit, saveBrain, cancelBrainEdit, savingBrain })}
|
|
969
|
-
${operatingCard({ policy, toggleStop, setBudget, setBand })}
|
|
1119
|
+
${operatingCard({ policy, toggleStop, setBudget, setBand, setAutonomy })}
|
|
970
1120
|
${!strategy ? html`<section class="sec-card">
|
|
971
1121
|
<h2 class="sec-h2">${t('secretary.strat.title')}</h2>
|
|
972
1122
|
<p class="sec-hint">${t('secretary.strat.enableHint')}</p>
|
|
@@ -987,6 +1137,37 @@ ${SECRETARY_AIMEAT_PRIMER}`;
|
|
|
987
1137
|
` : null}
|
|
988
1138
|
` : null}`}
|
|
989
1139
|
${calEvent ? html`<${CalendarEventDialog} event=${calEvent} onClose=${() => setCalEvent(null)} triggers=${{ update: trig.updateTrigger, togglePause: trig.togglePause, remove: trig.removeTrigger }} workflows=${trig.workflows} specialists=${spec.list} routines=${{ setCadence: next.setRoutineCadence, setStatus: next.setRoutineStatus }} tick=${{ toggle: auto.toggleTick, run: auto.runTick }} feed=${{ remove: auto.deleteFeedItem }} />` : null}
|
|
1140
|
+
${chooseMode ? html`
|
|
1141
|
+
<div class="sec-setup-overlay" onClick=${(e) => { if (e.target === e.currentTarget) setChooseMode(false); }}>
|
|
1142
|
+
<div class="sec-setup-box sec-mode-box">
|
|
1143
|
+
<h3 class="sec-h2">${t('secretary.mode.title')}</h3>
|
|
1144
|
+
<p class="sec-desc">${t('secretary.mode.intro')}</p>
|
|
1145
|
+
${[['draft', t('secretary.mode.draftTitle'), t('secretary.mode.draftDesc')], ['act', t('secretary.mode.actTitle'), t('secretary.mode.actDesc')], ['off', t('secretary.mode.offTitle'), t('secretary.mode.offDesc')]].map(([val, title, desc]) => html`
|
|
1146
|
+
<label class=${'sec-mode-opt' + (setupMode === val ? ' sec-mode-opt--on' : '')}>
|
|
1147
|
+
<input type="radio" name="setupmode" checked=${setupMode === val} onChange=${() => setSetupMode(val)} />
|
|
1148
|
+
<span><span class="sec-mode-opt-t">${title}</span><span class="sec-desc">${desc}</span></span>
|
|
1149
|
+
</label>`)}
|
|
1150
|
+
<p class="sec-hint">${t('secretary.mode.changeLater')}</p>
|
|
1151
|
+
<div class="sec-actions">
|
|
1152
|
+
<button class="btn-ghost btn-sm" onClick=${() => setChooseMode(false)}>${t('secretary.cancel')}</button>
|
|
1153
|
+
<button class="btn-primary" onClick=${confirmSetup}>${t('secretary.apply')}</button>
|
|
1154
|
+
</div>
|
|
1155
|
+
</div>
|
|
1156
|
+
</div>` : null}
|
|
1157
|
+
${applying && setupStep ? (() => {
|
|
1158
|
+
// Top-level setup-progress overlay — stays visible across the hire→dashboard switch (persistConfig
|
|
1159
|
+
// makes the Secretary "exist" before the slow fill, so a panel-local progress view would unmount).
|
|
1160
|
+
const s = setupStep, pct = s.total ? Math.round(((s.done || 0) / s.total) * 100) : null;
|
|
1161
|
+
const msg = s.phase === 'fill'
|
|
1162
|
+
? `${t('secretary.stepFilling')} ${(s.done || 0) + 1}/${s.total}${s.name ? ' · ' + s.name : ''}`
|
|
1163
|
+
: (s.phase === 'done' ? t('secretary.stepDone') : t('secretary.stepStructure'));
|
|
1164
|
+
return html`<div class="sec-setup-overlay"><div class="sec-setup-box">
|
|
1165
|
+
<h3 class="sec-h2">${t('secretary.settingUpTitle')}</h3>
|
|
1166
|
+
<div class="sec-setup-row"><span class="sec-spin"></span><span>${msg}</span></div>
|
|
1167
|
+
${pct != null ? html`<div class="sec-progress-bar"><div class="sec-progress-fill" style=${`width:${pct}%`}></div></div>` : null}
|
|
1168
|
+
<p class="sec-desc">${t('secretary.settingUpHint')}</p>
|
|
1169
|
+
</div></div>`;
|
|
1170
|
+
})() : null}
|
|
990
1171
|
<${ToastContainer} />
|
|
991
1172
|
</div>`;
|
|
992
1173
|
}
|