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
3 changes: 3 additions & 0 deletions electron-builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ asarUnpack:
# level deeper than the entry point: mcp-bots.js unpacked fine and still died on
# `Cannot find module './bots.js'`. Pinned by test/asar-helpers.test.js.
- "bots.js"
# One level deeper the same trap: mcp-task-manager.js requires the task-text limit,
# so that module has to sit outside the archive with it.
- "task-text-limit.js"

files:
- "**/*"
Expand Down
5 changes: 4 additions & 1 deletion mcp-task-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@

const http = require('http');
const { StringDecoder } = require('string_decoder');
// Same limit the HTTP API enforces; advertising a smaller one here makes the model
// shorten a task for no reason.
const { MAX_TASK_TEXT } = require('./task-text-limit.js');

const SERVER_URL = process.env.TASK_MANAGER_SERVER_URL || 'http://127.0.0.1:3000';
const TASK_ID = process.env.TASK_MANAGER_TASK_ID || '';
Expand Down Expand Up @@ -42,7 +45,7 @@ const TOOLS = [
type: 'object',
properties: {
title: { type: 'string', description: 'Short task title (max 200 chars)' },
description: { type: 'string', description: 'Detailed task description (max 2000 chars)' },
description: { type: 'string', description: `Detailed task description (max ${MAX_TASK_TEXT} chars)` },
status: { type: 'string', enum: ['backlog', 'todo', 'done'], description: 'todo = queue it for execution now (default). backlog = add to the board only, nothing runs. done = an already-finished item, e.g. a "- [x]" line from a plan you are importing.' },
context: {
description: 'Curated context to pass to the child task. Include ONLY what the child needs — issue details, code snippets, error messages, etc. Can be a string or a JSON object. The child reads this via get_current_task.',
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"scripts": {
"start": "node server.js",
"dev": "node --watch server.js",
"test": "node --test test/render/*.test.mjs && node test/overload-detector.test.js && node test/usage-limit.test.js && node test/auth-errors.test.js && node test/env-load-order.test.js && node test/multi-agent-result.test.js && node test/terminal-session.test.js && node test/terminal-schema.test.js && node test/bots.test.js && node test/bot-inbox.test.js && node test/bots-api.test.js && node test/running-sessions.test.js && node test/session-liveness.test.js && node test/terminal-bridge.integration.test.js && node test/tmux-composite.test.js && node test/engine-spawn-cmd.test.js && node test/claude-firstrun.test.js && node test/telegram-format.test.js && node test/telegram-behaviour.test.js && node test/ask-user-question.test.js && node test/delegate-terminal.test.js && node test/agent-catalog.test.js && node test/update-flow.test.js && node test/kanban-schedule.test.js && node test/kanban-run-badges.test.js && node test/task-backlog.test.js && node test/i18n-completeness.test.js && node test/config-resolve.test.js && node test/chat-defaults.test.js && node test/chat-defaults-api.test.js && node test/run-continuation.test.js && node test/worktree-manager.test.js && node test/session-expiry.test.js && node test/setup-gate.test.js && node test/path-guard.test.js && node test/subscribe-nocatchup.test.js && node test/cli-import-remote.test.js && node test/remote-list-parse.test.js && node test/config-migrate.test.js && node test/engine-pane.test.js && node test/global-workspace.test.js && node test/ssh-secret.test.js && node test/agent-dag.test.js && node test/ssh-parser.test.js && node test/ssh-termination.test.js && node test/session-restart.test.js && node test/remote-env.test.js && node test/remote-files.test.js && node test/remote-files-api.test.js && node test/auth-token.test.js && node test/cmd-model.test.js && node test/origin-guard.test.js && node test/interrupted-recovery.test.js && node test/queue-persistence.test.js && node test/interrupt-idle-config.test.js && node test/agents-md.test.js && node test/editor-links.test.js && node test/editor-open-api.test.js && node test/interrupt-delivery.test.js && node test/terminal-pane-guard.integration.test.js && node test/composer-terminal.test.js && node test/claude-cli-status.test.js && node test/asar-helpers.test.js && node test/mac-signing.test.js && node test/release-mac.test.js && node test/task-workers.test.js && node test/git-clone.test.js && node test/responsive-ui.test.js && node test/kanban-delegation.test.js && node test/kanban-group-button.test.js && node test/terminal-keys.test.js && node test/electron-navigation.test.js && node test/cli-runtime.test.js && node test/worktree-safety.test.js && node test/auth-boundary.test.js && node test/cli-process-tree.test.js && node test/schedule-actions.test.js && node test/install-hooks.test.js && node test/frontend-recovery.test.js && node test/db-revisions.test.js && node test/frontend-refresh-races.test.js && node test/release-gate.test.js && node test/focused-workspace.test.js && node test/secondary-navigation.test.js && node test/section-controls-ux.test.js && node test/settings-ux.test.js && node test/team-forms-ux.test.js && node test/page-forms-ux.test.js && node test/sidebar-ux.test.js",
"test": "node --test test/render/*.test.mjs && node test/overload-detector.test.js && node test/usage-limit.test.js && node test/auth-errors.test.js && node test/env-load-order.test.js && node test/multi-agent-result.test.js && node test/terminal-session.test.js && node test/terminal-schema.test.js && node test/bots.test.js && node test/bot-inbox.test.js && node test/bots-api.test.js && node test/running-sessions.test.js && node test/session-liveness.test.js && node test/terminal-bridge.integration.test.js && node test/tmux-composite.test.js && node test/engine-spawn-cmd.test.js && node test/claude-firstrun.test.js && node test/telegram-format.test.js && node test/telegram-behaviour.test.js && node test/ask-user-question.test.js && node test/delegate-terminal.test.js && node test/agent-catalog.test.js && node test/update-flow.test.js && node test/kanban-schedule.test.js && node test/kanban-run-badges.test.js && node test/task-backlog.test.js && node test/i18n-completeness.test.js && node test/config-resolve.test.js && node test/chat-defaults.test.js && node test/chat-defaults-api.test.js && node test/run-continuation.test.js && node test/worktree-manager.test.js && node test/session-expiry.test.js && node test/setup-gate.test.js && node test/path-guard.test.js && node test/subscribe-nocatchup.test.js && node test/cli-import-remote.test.js && node test/remote-list-parse.test.js && node test/config-migrate.test.js && node test/engine-pane.test.js && node test/global-workspace.test.js && node test/ssh-secret.test.js && node test/agent-dag.test.js && node test/ssh-parser.test.js && node test/ssh-termination.test.js && node test/session-restart.test.js && node test/remote-env.test.js && node test/remote-files.test.js && node test/remote-files-api.test.js && node test/auth-token.test.js && node test/cmd-model.test.js && node test/origin-guard.test.js && node test/interrupted-recovery.test.js && node test/queue-persistence.test.js && node test/interrupt-idle-config.test.js && node test/agents-md.test.js && node test/editor-links.test.js && node test/editor-open-api.test.js && node test/interrupt-delivery.test.js && node test/terminal-pane-guard.integration.test.js && node test/composer-terminal.test.js && node test/claude-cli-status.test.js && node test/asar-helpers.test.js && node test/mac-signing.test.js && node test/release-mac.test.js && node test/task-workers.test.js && node test/git-clone.test.js && node test/responsive-ui.test.js && node test/kanban-delegation.test.js && node test/kanban-group-button.test.js && node test/terminal-keys.test.js && node test/electron-navigation.test.js && node test/cli-runtime.test.js && node test/worktree-safety.test.js && node test/auth-boundary.test.js && node test/cli-process-tree.test.js && node test/schedule-actions.test.js && node test/install-hooks.test.js && node test/frontend-recovery.test.js && node test/db-revisions.test.js && node test/frontend-refresh-races.test.js && node test/release-gate.test.js && node test/focused-workspace.test.js && node test/secondary-navigation.test.js && node test/section-controls-ux.test.js && node test/settings-ux.test.js && node test/team-forms-ux.test.js && node test/page-forms-ux.test.js && node test/sidebar-ux.test.js && node test/task-text-limit.test.js",
"postinstall": "node scripts/install-hooks.js",
"release": "node scripts/release.js",
"release:mac": "node scripts/release-mac.js",
Expand Down
31 changes: 24 additions & 7 deletions public/kanban.html
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,8 @@
.modal-body .sc-sep { display:none; }
.modal-body .sc-btn { min-height:32px; }
.modal-body :is(input,select,textarea,button):focus-visible { outline:2px solid var(--accent); outline-offset:3px; }
.inp-hint{font-size:11px;color:var(--muted);margin-top:4px;line-height:1.4}
.inp-hint.over{color:var(--red)}
.modal-body .inp-hint,.modal-body .sched-hint { line-height:1.6; margin-top:6px; }
@media(max-width:600px){
.modal-body .grid2 { grid-template-columns:1fr; }
Expand Down Expand Up @@ -1030,6 +1032,19 @@
document.documentElement.lang = lang;
document.documentElement.dir = (lang === 'he') ? 'rtl' : 'ltr';

// ─── Task text limit ───────────────────────────────────────────────────────
// The number server.js stores and mcp-task-manager.js advertises (task-text-limit.js).
// A maxlength attribute cuts a paste without a word, so a field that holds prose
// also carries a counter: it reaches MAX_TASK_TEXT exactly where the text was cut.
const MAX_TASK_TEXT = 20000;
function updTextCount(id, outId) {
const el = $i(id), out = $i(outId);
if (!el || !out) return;
const n = el.value.length;
out.textContent = n + ' / ' + MAX_TASK_TEXT;
out.classList.toggle('over', n >= MAX_TASK_TEXT);
}

// Declarative i18n — same attribute contract as index.html: the markup carries the
// key, this pass fills in text/attributes for the active language. Every user-facing
// literal in the markup must be annotated; test/i18n-completeness.test.js enforces it.
Expand Down Expand Up @@ -1952,7 +1967,7 @@
<input id="fChainTaskTitle" class="inp" placeholder="${t('modal.name')}" maxlength="200">
</div>
<div><label class="lbl" for="fChainTaskDesc">${t('modal.desc')}</label>
<textarea id="fChainTaskDesc" class="inp" rows="3" placeholder="…" maxlength="2000"></textarea>
<textarea id="fChainTaskDesc" class="inp" rows="3" placeholder="…" maxlength="${MAX_TASK_TEXT}"></textarea>
</div>`;
$i('modalFt').innerHTML=`
<button class="btn btn-ghost btn-sm" onclick="closeModal()">${t('modal.cancel')}</button>
Expand All @@ -1973,7 +1988,7 @@
<span class="task-num">1.</span>
<div class="task-fields">
<input class="chain-task-input" aria-label="${escH(t('modal.name'))}" placeholder="${escH(t('modal.name'))}" maxlength="200">
<textarea class="chain-task-desc-input" aria-label="${escH(t('modal.desc'))}" rows="2" placeholder="${escH(t('modal.desc'))}" maxlength="2000"></textarea>
<textarea class="chain-task-desc-input" aria-label="${escH(t('modal.desc'))}" rows="2" placeholder="${escH(t('modal.desc'))}" maxlength="${MAX_TASK_TEXT}"></textarea>
</div>
<button class="task-remove" aria-label="${escH(t('modal.delete'))}" onclick="removeChainFormTask(this)">✕</button>
</div>
Expand All @@ -1990,7 +2005,7 @@
<span class="task-num">${i+1}.</span>
<div class="task-fields">
<input class="chain-task-input" aria-label="${escH(t('modal.name'))}" placeholder="${escH(t('modal.name'))}" maxlength="200" value="${escH(tk.title)}">
<textarea class="chain-task-desc-input" aria-label="${escH(t('modal.desc'))}" rows="2" placeholder="${escH(t('modal.desc'))}" maxlength="2000">${escH(tk.description||'')}</textarea>
<textarea class="chain-task-desc-input" aria-label="${escH(t('modal.desc'))}" rows="2" placeholder="${escH(t('modal.desc'))}" maxlength="${MAX_TASK_TEXT}">${escH(tk.description||'')}</textarea>
</div>
<button class="task-remove" aria-label="${escH(t('modal.delete'))}" onclick="removeChainFormTask(this)">✕</button>
</div>`:`
Expand Down Expand Up @@ -2069,7 +2084,7 @@
<span class="task-num">${count+1}.</span>
<div class="task-fields">
<input class="chain-task-input" aria-label="${escH(t('modal.name'))}" placeholder="${escH(t('modal.name'))}" maxlength="200">
<textarea class="chain-task-desc-input" aria-label="${escH(t('modal.desc'))}" rows="2" placeholder="${escH(t('modal.desc'))}" maxlength="2000"></textarea>
<textarea class="chain-task-desc-input" aria-label="${escH(t('modal.desc'))}" rows="2" placeholder="${escH(t('modal.desc'))}" maxlength="${MAX_TASK_TEXT}"></textarea>
</div>
<button class="task-remove" aria-label="${escH(t('modal.delete'))}" onclick="removeChainFormTask(this)">✕</button>`;
container.appendChild(div);
Expand Down Expand Up @@ -2224,7 +2239,7 @@
<input id="fChainTaskTitle" class="inp" placeholder="${t('modal.name')}" maxlength="200" value="${escH(tk.title)}"${dis}>
</div>
<div><label class="lbl" for="fChainTaskDesc">${t('modal.desc')}</label>
<textarea id="fChainTaskDesc" class="inp" rows="3" placeholder="…" maxlength="2000"${dis}>${escH(tk.description||'')}</textarea>
<textarea id="fChainTaskDesc" class="inp" rows="3" placeholder="…" maxlength="${MAX_TASK_TEXT}"${dis}>${escH(tk.description||'')}</textarea>
</div>`;
$i('modalFt').innerHTML=editable?`
<button class="btn btn-ghost btn-sm" onclick="closeModal()">${t('modal.cancel')}</button>
Expand Down Expand Up @@ -2457,10 +2472,12 @@
</div>
</div>
<div><label class="lbl" for="fDesc">${t('modal.desc')}</label>
<textarea id="fDesc" class="inp" rows="3" placeholder="…" maxlength="2000">${escH(tk.description||'')}</textarea>
<textarea id="fDesc" class="inp" rows="3" placeholder="…" maxlength="${MAX_TASK_TEXT}" oninput="updTextCount('fDesc','fDescCount')">${escH(tk.description||'')}</textarea>
<div class="inp-hint" id="fDescCount">${(tk.description||'').length} / ${MAX_TASK_TEXT}</div>
</div>
<div><label class="lbl" for="fNotes">${t('modal.notes')}</label>
<textarea id="fNotes" class="inp" rows="2" placeholder="…" maxlength="2000">${escH(tk.notes||'')}</textarea>
<textarea id="fNotes" class="inp" rows="2" placeholder="…" maxlength="${MAX_TASK_TEXT}" oninput="updTextCount('fNotes','fNotesCount')">${escH(tk.notes||'')}</textarea>
<div class="inp-hint" id="fNotesCount">${(tk.notes||'').length} / ${MAX_TASK_TEXT}</div>
</div>
<div>
<label class="lbl" for="fAttachments">${t('modal.attach')}</label>
Expand Down
19 changes: 17 additions & 2 deletions public/schedule.html
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@
/* ─── Card body ─── */
.agcard-body{flex:1;min-width:0}
.agcard-title{font-size:14px;font-weight:600;color:var(--text);margin-bottom:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;letter-spacing:-.1px}
.agcard-desc{font-size:12px;color:var(--muted);margin-bottom:7px;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden;line-height:1.5}
.agcard-desc{font-size:12px;color:var(--muted);margin-bottom:7px;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;line-height:1.5}
.agcard-badges{display:flex;flex-wrap:wrap;gap:4px;align-items:center}

/* ─── Badges ─── */
Expand Down Expand Up @@ -232,6 +232,7 @@
.inp[type="date"]{color-scheme:dark}
.inp[type="date"]::-webkit-calendar-picker-indicator{filter:invert(.5) sepia(1) saturate(5) hue-rotate(200deg);cursor:pointer;opacity:.6}
.inp-hint{font-size:11px;color:var(--muted);margin-top:4px;line-height:1.4}
.inp-hint.over{color:var(--red)}
.sel{width:100%;background:var(--s2);border:1px solid var(--border);border-radius:var(--r-sm);color:var(--text);font-size:13px;padding:8px 12px;font-family:inherit;cursor:pointer;transition:border-color .15s;appearance:none;-webkit-appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%237a8baa' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 10px center}
.sel:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px rgba(124,106,239,.12)}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:12px}
Expand Down Expand Up @@ -585,6 +586,19 @@
let lang = localStorage.getItem('lang') || 'en';
document.documentElement.lang = lang;
document.documentElement.dir = (lang === 'he') ? 'rtl' : 'ltr';

// ─── Task text limit ───────────────────────────────────────────────────────
// The number server.js stores and mcp-task-manager.js advertises (task-text-limit.js).
// A maxlength attribute cuts a paste without a word, so a field that holds prose
// also carries a counter: it reaches MAX_TASK_TEXT exactly where the text was cut.
const MAX_TASK_TEXT = 20000;
function updTextCount(id, outId) {
const el = $i(id), out = $i(outId);
if (!el || !out) return;
const n = el.value.length;
out.textContent = n + ' / ' + MAX_TASK_TEXT;
out.classList.toggle('over', n >= MAX_TASK_TEXT);
}
const TR = {
uk: {
'nav.back': 'Назад',
Expand Down Expand Up @@ -1646,7 +1660,8 @@
</div>
<div>
<label class="lbl" for="fDesc">${t('form.desc')}</label>
<textarea id="fDesc" class="inp" rows="3" placeholder="${t('form.desc.ph')}" maxlength="2000">${escH(tk.description||'')}</textarea>
<textarea id="fDesc" class="inp" rows="3" placeholder="${t('form.desc.ph')}" maxlength="${MAX_TASK_TEXT}" oninput="updTextCount('fDesc','fDescCount')">${escH(tk.description||'')}</textarea>
<div class="inp-hint" id="fDescCount">${(tk.description||'').length} / ${MAX_TASK_TEXT}</div>
</div>
<div>
<label class="lbl" for="fSession">${t('form.session')}</label>
Expand Down
Loading
Loading