dsh-composer-plus 1.21.0 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.de.md +6 -5
- package/README.es.md +6 -5
- package/README.fr.md +6 -5
- package/README.hi.md +6 -5
- package/README.id.md +6 -5
- package/README.ja.md +6 -5
- package/README.ko.md +6 -5
- package/README.md +6 -5
- package/README.pt-BR.md +6 -5
- package/README.ru.md +6 -5
- package/README.tr.md +6 -5
- package/README.vi.md +6 -5
- package/README.zh.md +6 -5
- package/client.js +107 -40
- package/index.js +70 -8
- package/package.json +2 -2
- package/release-notes.json +14 -14
- package/tether/gateway/server.mjs +129 -8
package/index.js
CHANGED
|
@@ -709,6 +709,19 @@ export const shippedNotes = (text, version = PACKAGE.version) => {
|
|
|
709
709
|
.map(([id, body]) => [id, releaseNotes(body)]).filter(([, body]) => body !== undefined));
|
|
710
710
|
return Object.keys(notes).length === 0 ? undefined : notes;
|
|
711
711
|
};
|
|
712
|
+
/** The notes that ship with this running copy, read once at start. A copy updated without a dsh restart runs this old Host
|
|
713
|
+
* next to a newer `release-notes.json` on disk; reading once keeps the running version's own notes (What's new). */
|
|
714
|
+
const SHIPPED_NOTES = (() => { try { return shippedNotes(readFileSync(new URL('./release-notes.json', import.meta.url), 'utf8')); } catch { return undefined; } })();
|
|
715
|
+
/** The version installed on disk (`package.json`), only when it is newer than the running copy: a copy updated without a
|
|
716
|
+
* dsh restart still runs the old Host, so the Settings row asks for a restart. undefined for an unreadable, broken or
|
|
717
|
+
* non-x.y.z file, a running version that is not x.y.z, or a disk version that is not newer. */
|
|
718
|
+
export const installedUpdate = (file = new URL('./package.json', import.meta.url), running = PACKAGE.version) => {
|
|
719
|
+
const now = parseVersion(running);
|
|
720
|
+
if (now === undefined) return undefined;
|
|
721
|
+
let version;
|
|
722
|
+
try { version = parseVersion(JSON.parse(readFileSync(file, 'utf8'))?.version); } catch { return undefined; }
|
|
723
|
+
return version !== undefined && newer(version, now) ? version.join('.') : undefined;
|
|
724
|
+
};
|
|
712
725
|
/** Latest GitHub release vs this copy. `managed` is false for a renamed or linked local copy, which pnpm can't upgrade. */
|
|
713
726
|
export async function checkUpdate(fetchImpl = fetch, pkg = PACKAGE, signal) {
|
|
714
727
|
const current = parseVersion(pkg.version);
|
|
@@ -842,7 +855,7 @@ export function unreadStore(file) {
|
|
|
842
855
|
|
|
843
856
|
/** Mobile connect: one gateway at a time. Start and stop calls run in order, so a fast toggle never leaves two gateways or a
|
|
844
857
|
* half-closed one. `upstreamUrl` gives dsh's authenticated loopback URL; it is asked again at each DSH login. */
|
|
845
|
-
export function mobileGateway({ upstreamUrl, dir, make = createGateway, retryMs = 300 }) {
|
|
858
|
+
export function mobileGateway({ upstreamUrl, dir, about, make = createGateway, retryMs = 300 }) {
|
|
846
859
|
let gateway, admin, error, closed = false, queue = Promise.resolve();
|
|
847
860
|
const run = (fn) => (queue = queue.then(fn, fn));
|
|
848
861
|
// A plugin reload starts the new gateway while the old one still closes its pairing page: wait for the port (about 3 s).
|
|
@@ -864,7 +877,7 @@ export function mobileGateway({ upstreamUrl, dir, make = createGateway, retryMs
|
|
|
864
877
|
if (gateway !== undefined || closed) return;
|
|
865
878
|
error = undefined;
|
|
866
879
|
try {
|
|
867
|
-
gateway = make({ dir, upstreamUrl });
|
|
880
|
+
gateway = make({ dir, upstreamUrl, about });
|
|
868
881
|
admin = `http://127.0.0.1:${await listenAdmin(gateway)}/`;
|
|
869
882
|
await gateway.start();
|
|
870
883
|
} catch (e) { error = String(e?.message ?? e); if (admin === undefined) await stop(); }
|
|
@@ -874,6 +887,8 @@ export function mobileGateway({ upstreamUrl, dir, make = createGateway, retryMs
|
|
|
874
887
|
close: () => { closed = true; return run(stop); },
|
|
875
888
|
/** An event for the connected phones (`$events`); nothing while no gateway runs. */
|
|
876
889
|
emit: (event, args) => { gateway?.emit?.(event, args); },
|
|
890
|
+
/** A push notification (APNs) for the phones that want one; nothing while no gateway runs. */
|
|
891
|
+
push: (title, detail, sessionId) => { gateway?.push?.(title, detail, sessionId); },
|
|
877
892
|
state: () => {
|
|
878
893
|
const running = gateway?.state().running === true;
|
|
879
894
|
// The gateway retries by itself (for example when Wi-Fi comes back): a running gateway has no error to show.
|
|
@@ -2170,13 +2185,17 @@ export function apply(ctx) {
|
|
|
2170
2185
|
ctx.connection.fetch.register({
|
|
2171
2186
|
path: UPDATE_PATH, methods: ['GET'], requestBody: 'buffered',
|
|
2172
2187
|
fetch: async (request) => {
|
|
2173
|
-
// `?current=1`: the version that this Host runs and its shipped release notes (What's new
|
|
2188
|
+
// `?current=1`: the version that this Host runs and its shipped release notes (What's new), read at start; no
|
|
2174
2189
|
// network call. A missing or broken notes file leaves `notes` out.
|
|
2175
2190
|
if (new URL(request.url).searchParams.get('current') === '1') {
|
|
2176
|
-
|
|
2177
|
-
return json(notes === undefined ? { current: PACKAGE.version } : { current: PACKAGE.version, notes });
|
|
2191
|
+
return json(SHIPPED_NOTES === undefined ? { current: PACKAGE.version } : { current: PACKAGE.version, notes: SHIPPED_NOTES });
|
|
2178
2192
|
}
|
|
2179
|
-
|
|
2193
|
+
// `installed`: a newer version on disk than the one that runs (updated without a restart).
|
|
2194
|
+
try {
|
|
2195
|
+
const info = await checkUpdate(fetch, PACKAGE, request.signal);
|
|
2196
|
+
const installed = installedUpdate();
|
|
2197
|
+
return json(installed === undefined ? info : { ...info, installed });
|
|
2198
|
+
} catch (error) { return json({ error: String(error?.message ?? error) }, 502); }
|
|
2180
2199
|
},
|
|
2181
2200
|
});
|
|
2182
2201
|
// Allow for this chat. `GET` answers the old shape (`{sessionId: [tool]}`, only grants that still run: the gateway and an
|
|
@@ -2208,10 +2227,25 @@ export function apply(ctx) {
|
|
|
2208
2227
|
let mobileOn; // the user's choice; the saved value applies only until the user changes it
|
|
2209
2228
|
const mobileRead = mobileSaved.read().then((on) => { mobileOn ??= on; });
|
|
2210
2229
|
let mobileQueue = Promise.resolve(); // one switch change at a time: save, then start or stop
|
|
2230
|
+
const serviceOf = (name) => { try { return ctx.get?.(name); } catch { return undefined; } };
|
|
2231
|
+
/** The words of a Tether push about a chat: its title and its project (dsh's project title, else the folder name; a
|
|
2232
|
+
* branch folder `<repo>/.worktrees/<name>` gives its repository's name). Empty for a chat that is not loaded. */
|
|
2233
|
+
const chatAbout = (sessionId) => {
|
|
2234
|
+
const session = serviceOf('agents')?.get?.(sessionId)?.session;
|
|
2235
|
+
if (session === undefined) return {};
|
|
2236
|
+
let chat, cwd, project;
|
|
2237
|
+
try { chat = ctx.sessionProjections?.snapshot?.(session, ['title'])?.values?.title; } catch { /* no title */ }
|
|
2238
|
+
try { cwd = serviceOf('workingDirectory')?.get?.(session); } catch { /* dsh 0.2.0 has none */ }
|
|
2239
|
+
if (!isText(cwd, 4096)) cwd = session.header?.cwd;
|
|
2240
|
+
let list;
|
|
2241
|
+
try { list = serviceOf('workspaceRegistry')?.list?.(); } catch { /* not ready */ }
|
|
2242
|
+
return { chat: isText(chat, 4096) ? chat : undefined, project: projectOf(cwd, list) };
|
|
2243
|
+
};
|
|
2211
2244
|
ctx.inject?.(['webServer'], (webCtx) => {
|
|
2212
2245
|
const gateway = mobileGateway({
|
|
2213
2246
|
dir: process.env.DSH_MOBILE_DIR || join(home, 'storages', 'dsh-composer-plus', 'mobile'),
|
|
2214
2247
|
upstreamUrl: () => ctx.connection.authenticatedUrl(`http://127.0.0.1:${webCtx.webServer.port}`),
|
|
2248
|
+
about: chatAbout,
|
|
2215
2249
|
});
|
|
2216
2250
|
mobile = gateway;
|
|
2217
2251
|
webCtx.effect(() => {
|
|
@@ -2282,7 +2316,14 @@ export function apply(ctx) {
|
|
|
2282
2316
|
ctx.on('agent/status', ({ agent, status }) => {
|
|
2283
2317
|
if (agent?.session?.header?.origin === 'subagent') return; // the parent chat shows its subagents' work
|
|
2284
2318
|
if (status === 'running') { busy.add(agent.id); if (mobileOn === true) changeUnread([], [agent.id]); }
|
|
2285
|
-
else if (busy.delete(agent.id) && mobileOn === true)
|
|
2319
|
+
else if (busy.delete(agent.id) && mobileOn === true) {
|
|
2320
|
+
changeUnread([agent.id], []);
|
|
2321
|
+
// Tether's "Turn finished" also while Tether is closed (APNs, when the gateway has a key): the agent's reply, under
|
|
2322
|
+
// the Mac, the project and the chat's title (chatAbout).
|
|
2323
|
+
let reply = '';
|
|
2324
|
+
try { reply = lastReply(agent.session?.snapshotEvents?.() ?? []); } catch { /* no log */ }
|
|
2325
|
+
mobile?.push('Turn finished', reply, agent.id);
|
|
2326
|
+
}
|
|
2286
2327
|
});
|
|
2287
2328
|
ctx.connection.fetch.register({
|
|
2288
2329
|
path: UNREAD_PATH, methods: ['GET', 'POST'], requestBody: 'buffered',
|
|
@@ -3064,6 +3105,26 @@ export const predictStore = (file) => settingsStore(file, (v) => ({ on: v?.on !=
|
|
|
3064
3105
|
|
|
3065
3106
|
/** Text blocks of a message content, joined. */
|
|
3066
3107
|
const textBlocks = (content) => (Array.isArray(content) ? content.filter((b) => b?.type === 'text' && typeof b.text === 'string').map((b) => b.text).join('') : '');
|
|
3108
|
+
/** A chat folder's project name in a Tether push: a branch folder `<repo>/.worktrees/<name>` gives its repository's
|
|
3109
|
+
* name, else dsh's project title (`workspaces`: `workspaceRegistry.list()`), else the folder name. */
|
|
3110
|
+
export function projectOf(cwd, workspaces) {
|
|
3111
|
+
if (!isText(cwd, 4096) || !isAbsolute(cwd)) return undefined;
|
|
3112
|
+
const folder = cwd.replace(/(.)[\\/]+$/, '$1');
|
|
3113
|
+
if (basename(dirname(folder)) === '.worktrees') return basename(dirname(dirname(folder))) || undefined;
|
|
3114
|
+
const w = Array.isArray(workspaces) ? workspaces.find((x) => x?.path === folder) : undefined;
|
|
3115
|
+
return isText(w?.title, 512) ? w.title : basename(folder) || undefined;
|
|
3116
|
+
}
|
|
3117
|
+
/** The agent's last reply text in the chat's last turn (Tether's Turn finished push), or '' when that turn has none.
|
|
3118
|
+
* A reply that a rollback withdrew does not count. */
|
|
3119
|
+
export function lastReply(events) {
|
|
3120
|
+
const start = events.findLastIndex((e) => e.type === 'turn/start');
|
|
3121
|
+
const gone = withdrawn(events);
|
|
3122
|
+
for (let i = events.length - 1; i > start; i--) {
|
|
3123
|
+
const text = events[i].type === 'assistant/message' && !gone(events[i].seq) ? textBlocks(events[i].data?.message?.content).trim() : '';
|
|
3124
|
+
if (text !== '') return text;
|
|
3125
|
+
}
|
|
3126
|
+
return '';
|
|
3127
|
+
}
|
|
3067
3128
|
/** The recent conversation for the prediction: the user's own messages and the agent's replies (no tools, no hidden
|
|
3068
3129
|
* context), oldest first, the newest kept when it is long. `undefined` when the chat has no reply after its last message. */
|
|
3069
3130
|
export function predictTranscript(events) {
|
|
@@ -5735,7 +5796,8 @@ function installDefaultWorkspace(ctx, home) {
|
|
|
5735
5796
|
* <dir>/journal.jsonl every change, with the line before and after
|
|
5736
5797
|
* <dir>/reviewed.json how far the review read each chat (event seq)
|
|
5737
5798
|
* Off by default: the review makes a model call after each finished turn. The Host keeps the switch (`memory.json`). */
|
|
5738
|
-
|
|
5799
|
+
// 2026-11-05 (owner): MEMORY.md 4000 → 8000 characters; each MEMORY.md goes into every chat's system prompt.
|
|
5800
|
+
export const MEMORY_LIMITS = { entry: 300, main: 8000, topic: 16000, topics: 40 };
|
|
5739
5801
|
export const MEMORY_TOOLS = ['memory_remember', 'memory_forget', 'memory_recall'];
|
|
5740
5802
|
const MEMORY_MAIN = 'MEMORY.md';
|
|
5741
5803
|
const MEMORY_EMPTY = '# Memory\n\n## Index\n';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-composer-plus",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.0",
|
|
4
4
|
"description": "DeepSeek Harness helpers: 11 extra UI languages (Türkçe, Español, Français, Deutsch, 日本語…), Changes panel, swipe-card feature ideas, sidebar search in message text, turn rollback with git file restore, delete chats to the Trash, input history, Shift+Tab /plan, one-click Continue, resume running chats after a restart, queue drag-and-drop, deeplinks, Auto Review allow-for-chat, notifications, iPhone access with the Tether app, automatic updates",
|
|
5
5
|
"icon": "./assets/icon.svg",
|
|
6
6
|
"license": "MIT",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"assets/features/*.webp"
|
|
80
80
|
],
|
|
81
81
|
"scripts": {
|
|
82
|
-
"test": "node scripts/build-locales.mjs --check && node scripts/build-docs.mjs --check && node test/locale.test.mjs && node test/continue.test.mjs && node test/continue-host.test.mjs && node test/resume-restart.test.mjs && node test/content-search.test.mjs && node test/turn-rollback.test.mjs && node test/turn-rollback-client.test.mjs && node test/delete-chat.test.mjs && node test/export-chat.test.mjs && node test/project-name.test.mjs && node test/queue-drag.test.mjs && node test/queue-move.test.mjs && node test/queue-edit.test.mjs && node test/send-first.test.mjs && node test/message-link.test.mjs && node test/mark-read.test.mjs && node test/plan-toggle.test.mjs && node test/keep-focus.test.mjs && node test/question-fit.test.mjs && node test/header-fit.test.mjs && node test/mobile-connect.test.mjs && node test/unread.test.mjs && node test/unread-client.test.mjs && node test/tether-gateway.test.mjs && node test/tether-parity.test.mjs && node test/tether-notify-per-mac.test.mjs && node test/deeplink.test.mjs && node test/chat-grant.test.mjs && node test/reject-note.test.mjs && node test/grant-ttl.test.mjs && node test/notify.test.mjs && node test/idle-switch.test.mjs && node test/next-waiting.test.mjs && node test/option-picks.test.mjs && node test/option-picks-ui.test.mjs && node test/changes.test.mjs && node test/chat-changes.test.mjs && node test/changes-tree.test.mjs && node test/file-chats.test.mjs && node test/file-chats-client.test.mjs && node test/feature-ideas.test.mjs && node test/feature-ideas-client.test.mjs && node test/github-issue.test.mjs && node test/project-status.test.mjs && node test/session-branch.test.mjs && node test/recent-projects.test.mjs && node test/project-tools.test.mjs && node test/has-selectors.test.mjs && node test/project-picker.test.mjs && node test/worktree-chats.test.mjs && node test/worktree-chats-client.test.mjs && node test/branch-tree.test.mjs && node test/branch-tree-client.test.mjs && node test/default-workspace.test.mjs && node test/default-workspace-client.test.mjs && node test/find-in-chat.test.mjs && node test/predict.test.mjs && node test/predict-client.test.mjs && node test/complete.test.mjs && node test/complete-client.test.mjs && node test/prompt-improve.test.mjs && node test/prompt-improve-client.test.mjs && node test/goal-check.test.mjs && node test/goal-check-client.test.mjs && node test/backup.test.mjs && node test/backup-host.test.mjs && node test/backup-client.test.mjs && node test/project-memory.test.mjs && node test/memory-editor.test.mjs && node test/memory-base.test.mjs && node test/memory-history.test.mjs && node test/memory-export.test.mjs && node test/memory-source.test.mjs && node test/memory-copy.test.mjs && node test/memory-stale.test.mjs && node test/memory-per-chat.test.mjs && node test/memory-suggest.test.mjs && node test/status-order.test.mjs && node test/toggles.test.mjs && node test/update.test.mjs && node test/release-notes.test.mjs && node test/active-count.test.mjs && node test/meta.test.mjs && node test/shots.test.mjs && node test/readme.test.mjs && node test/readme-labels.test.mjs && node test/feature-docs.test.mjs && node test/routes.test.mjs && node test/protected-files.test.mjs && node test/blocked-commands.test.mjs && node test/blocked-commands-client.test.mjs && node test/handoff.test.mjs && node test/context-breakdown.test.mjs && node test/task-board.test.mjs && node test/task-board-client.test.mjs && node test/planned-steps.test.mjs && node --test --test-timeout=120000 test/computers.test.mjs test/computers-host.test.mjs && node test/computers-client.test.mjs && node test/secret-input.test.mjs && node test/secret-input-client.test.mjs && node test/quote-reply.test.mjs && node test/side-chat.test.mjs && node test/diff-comments.test.mjs",
|
|
82
|
+
"test": "node scripts/build-locales.mjs --check && node scripts/build-docs.mjs --check && node test/locale.test.mjs && node test/continue.test.mjs && node test/continue-host.test.mjs && node test/resume-restart.test.mjs && node test/content-search.test.mjs && node test/turn-rollback.test.mjs && node test/turn-rollback-client.test.mjs && node test/delete-chat.test.mjs && node test/export-chat.test.mjs && node test/project-name.test.mjs && node test/queue-drag.test.mjs && node test/queue-move.test.mjs && node test/queue-edit.test.mjs && node test/send-first.test.mjs && node test/message-link.test.mjs && node test/mark-read.test.mjs && node test/plan-toggle.test.mjs && node test/keep-focus.test.mjs && node test/question-fit.test.mjs && node test/header-fit.test.mjs && node test/mobile-connect.test.mjs && node test/unread.test.mjs && node test/unread-client.test.mjs && node test/tether-gateway.test.mjs && node test/tether-parity.test.mjs && node test/tether-notify-per-mac.test.mjs && node test/tether-push-words.test.mjs && node test/deeplink.test.mjs && node test/chat-grant.test.mjs && node test/reject-note.test.mjs && node test/grant-ttl.test.mjs && node test/notify.test.mjs && node test/idle-switch.test.mjs && node test/next-waiting.test.mjs && node test/option-picks.test.mjs && node test/option-picks-ui.test.mjs && node test/changes.test.mjs && node test/chat-changes.test.mjs && node test/changes-tree.test.mjs && node test/file-chats.test.mjs && node test/file-chats-client.test.mjs && node test/feature-ideas.test.mjs && node test/feature-ideas-client.test.mjs && node test/github-issue.test.mjs && node test/project-status.test.mjs && node test/session-branch.test.mjs && node test/recent-projects.test.mjs && node test/project-tools.test.mjs && node test/has-selectors.test.mjs && node test/project-picker.test.mjs && node test/worktree-chats.test.mjs && node test/worktree-chats-client.test.mjs && node test/branch-tree.test.mjs && node test/branch-tree-client.test.mjs && node test/default-workspace.test.mjs && node test/default-workspace-client.test.mjs && node test/find-in-chat.test.mjs && node test/predict.test.mjs && node test/predict-client.test.mjs && node test/complete.test.mjs && node test/complete-client.test.mjs && node test/prompt-improve.test.mjs && node test/prompt-improve-client.test.mjs && node test/goal-check.test.mjs && node test/goal-check-client.test.mjs && node test/backup.test.mjs && node test/backup-host.test.mjs && node test/backup-client.test.mjs && node test/project-memory.test.mjs && node test/memory-editor.test.mjs && node test/memory-base.test.mjs && node test/memory-history.test.mjs && node test/memory-export.test.mjs && node test/memory-source.test.mjs && node test/memory-copy.test.mjs && node test/memory-stale.test.mjs && node test/memory-per-chat.test.mjs && node test/memory-suggest.test.mjs && node test/status-order.test.mjs && node test/toggles.test.mjs && node test/update.test.mjs && node test/whats-new-current.test.mjs && node test/release-notes.test.mjs && node test/active-count.test.mjs && node test/meta.test.mjs && node test/shots.test.mjs && node test/readme.test.mjs && node test/readme-labels.test.mjs && node test/feature-docs.test.mjs && node test/routes.test.mjs && node test/protected-files.test.mjs && node test/blocked-commands.test.mjs && node test/blocked-commands-client.test.mjs && node test/handoff.test.mjs && node test/context-breakdown.test.mjs && node test/task-board.test.mjs && node test/task-board-client.test.mjs && node test/planned-steps.test.mjs && node --test --test-timeout=120000 test/computers.test.mjs test/computers-host.test.mjs && node test/computers-client.test.mjs && node test/secret-input.test.mjs && node test/secret-input-client.test.mjs && node test/quote-reply.test.mjs && node test/side-chat.test.mjs && node test/diff-comments.test.mjs",
|
|
83
83
|
"build:locales": "node scripts/build-locales.mjs",
|
|
84
84
|
"build:readme": "node scripts/build-docs.mjs",
|
|
85
85
|
"prepublishOnly": "npm test"
|
package/release-notes.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.
|
|
2
|
+
"version": "1.22.0",
|
|
3
3
|
"notes": {
|
|
4
|
-
"en": "## New\n\n- **
|
|
5
|
-
"zh": "## 新功能\n\n-
|
|
6
|
-
"tr": "## Yeni\n\n- **
|
|
7
|
-
"es": "## Novedades\n\n- **
|
|
8
|
-
"pt-BR": "## Novidades\n\n- **
|
|
9
|
-
"fr": "## Nouveautés\n\n- **
|
|
10
|
-
"de": "## Neu\n\n- **
|
|
11
|
-
"ru": "## Новое\n\n-
|
|
12
|
-
"ja": "## 新機能\n\n-
|
|
13
|
-
"ko": "## 새 기능\n\n-
|
|
14
|
-
"id": "## Baru\n\n- **
|
|
15
|
-
"vi": "## Mới\n\n- **
|
|
16
|
-
"hi": "## नया\n\n-
|
|
4
|
+
"en": "## New\n\n- **Mobile connect (iPhone)**: Tether notifications can come also while Tether is closed. Put an APNs key in `apns.json` on the Mac (see `tether/README.md`). The Mac then sends the approval, question, error and Turn finished notifications through Apple’s push service. The Mac must be awake with dsh open. Each notification shows the Mac and the chat’s project (`Studio Mac · recipe-box`) and the chat’s title. Turn finished shows the agent’s last reply. This needs a Tether build from this version.\n\n## Fixes\n\n- **Secret input**: the card of a secret request no longer stays inside a closed step group. In the desktop view, the card opens its group, so you see it at once.\n- In a narrow chat, the approval card with **Allow for this chat** and **Reject with note…** puts its buttons on more lines. Each label stays on one line in its button.",
|
|
5
|
+
"zh": "## 新功能\n\n- **手机连接(iPhone)**:Tether 关闭时也能收到 Tether 通知。在 Mac 上把 APNs 密钥放入 `apns.json`(见 `tether/README.md`)。之后 Mac 通过 Apple 推送服务发送审批、提问、错误和 Turn finished 通知。Mac 必须处于唤醒状态并打开 dsh。每条通知显示 Mac 和对话的项目(`Studio Mac · recipe-box`)以及对话标题。Turn finished 显示代理的最后一条回复。这需要此版本的 Tether 构建。\n\n## 修复\n\n- **密钥输入**:密钥请求的卡片不再停留在折叠的步骤组中。在桌面视图中,卡片会展开它所在的组,让你立即看到它。\n- 在较窄的对话中,带有 **本对话内允许** 和 **拒绝并附言…** 的审批卡片会把按钮分成多行。每个按钮的文字保持在一行内。",
|
|
6
|
+
"tr": "## Yeni\n\n- **Mobil bağlantı (iPhone)**: Tether bildirimleri Tether kapalıyken de gelebilir. Mac’te `apns.json` dosyasına bir APNs anahtarı koyun (bkz. `tether/README.md`). Mac bundan sonra onay, soru, hata ve Turn finished bildirimlerini Apple’ın push hizmetiyle gönderir. Mac uyanık ve dsh açık olmalıdır. Her bildirim Mac’i ve sohbetin projesini (`Studio Mac · recipe-box`) ve sohbetin başlığını gösterir. Turn finished, ajanın son yanıtını gösterir. Bunun için bu sürümden bir Tether derlemesi gerekir.\n\n## Düzeltmeler\n\n- **Gizli değer girişi**: gizli değer isteğinin kartı artık kapalı bir adım grubunun içinde kalmaz. Masaüstü görünümünde kart kendi grubunu açar; böylece kartı hemen görürsünüz.\n- Dar bir sohbette, **Bu sohbet boyunca izin ver** ve **Not ile reddet…** düğmelerinin olduğu onay kartı düğmelerini birden çok satıra dizer. Her düğmenin yazısı tek satırda kalır.",
|
|
7
|
+
"es": "## Novedades\n\n- **Conexión móvil (iPhone)**: las notificaciones de Tether pueden llegar también con Tether cerrado. Pon una clave de APNs en `apns.json` en el Mac (consulta `tether/README.md`). Entonces el Mac envía las notificaciones de aprobación, pregunta, error y Turn finished mediante el servicio de notificaciones push de Apple. El Mac debe estar activo y con dsh abierto. Cada notificación muestra el Mac y el proyecto del chat (`Studio Mac · recipe-box`) y el título del chat. Turn finished muestra la última respuesta del agente. Hace falta una compilación de Tether de esta versión.\n\n## Correcciones\n\n- **Entrada secreta**: la tarjeta de una solicitud de secreto ya no se queda dentro de un grupo de pasos cerrado. En la vista de escritorio, la tarjeta abre su grupo, así que la ves enseguida.\n- En un chat estrecho, la tarjeta de aprobación con **Permitir en este chat** y **Rechazar con nota…** reparte sus botones en varias líneas. Cada texto queda en una sola línea dentro de su botón.",
|
|
8
|
+
"pt-BR": "## Novidades\n\n- **Conexão móvel (iPhone)**: as notificações do Tether podem chegar também com o Tether fechado. Coloque uma chave de APNs em `apns.json` no Mac (veja `tether/README.md`). Então o Mac envia as notificações de aprovação, pergunta, erro e Turn finished pelo serviço de notificações push da Apple. O Mac precisa estar ativo e com o dsh aberto. Cada notificação mostra o Mac e o projeto do chat (`Studio Mac · recipe-box`) e o título do chat. Turn finished mostra a última resposta do agente. É preciso uma build do Tether desta versão.\n\n## Correções\n\n- **Entrada secreta**: o cartão de um pedido de segredo não fica mais dentro de um grupo de etapas fechado. Na visualização do desktop, o cartão abre o grupo dele, então você o vê na hora.\n- Em um chat estreito, o cartão de aprovação com **Permitir neste chat** e **Rejeitar com nota…** distribui os botões em mais linhas. Cada texto fica em uma linha dentro do botão.",
|
|
9
|
+
"fr": "## Nouveautés\n\n- **Connexion mobile (iPhone)** : les notifications de Tether peuvent arriver aussi quand Tether est fermé. Placez une clé APNs dans `apns.json` sur le Mac (voir `tether/README.md`). Le Mac envoie alors les notifications d’approbation, de question, d’erreur et Turn finished par le service de notifications push d’Apple. Le Mac doit être éveillé, avec dsh ouvert. Chaque notification indique le Mac et le projet de la discussion (`Studio Mac · recipe-box`) ainsi que le titre de la discussion. Turn finished affiche la dernière réponse de l’agent. Il faut une version de Tether compilée à partir de cette version.\n\n## Corrections\n\n- **Saisie secrète** : la carte d’une demande de secret ne reste plus dans un groupe d’étapes fermé. Dans la vue bureau, la carte ouvre son groupe, vous la voyez donc tout de suite.\n- Dans une discussion étroite, la carte d’approbation avec **Autoriser pour cette discussion** et **Refuser avec une note…** répartit ses boutons sur plusieurs lignes. Chaque libellé reste sur une ligne dans son bouton.",
|
|
10
|
+
"de": "## Neu\n\n- **Mobile Verbindung (iPhone)**: Tether-Benachrichtigungen kommen jetzt auch, wenn Tether geschlossen ist. Lege auf dem Mac einen APNs-Schlüssel in `apns.json` ab (siehe `tether/README.md`). Der Mac sendet dann die Benachrichtigungen zu Freigabe, Frage, Fehler und Turn finished über Apples Push-Dienst. Der Mac muss wach sein und dsh offen haben. Jede Benachrichtigung zeigt den Mac und das Projekt des Chats (`Studio Mac · recipe-box`) sowie den Titel des Chats. Turn finished zeigt die letzte Antwort des Agenten. Dafür ist ein Tether-Build aus dieser Version nötig.\n\n## Korrekturen\n\n- **Geheime Eingabe**: Die Karte einer Geheimnis-Anfrage bleibt nicht mehr in einer geschlossenen Schrittgruppe. In der Desktop-Ansicht öffnet die Karte ihre Gruppe, sodass du sie sofort siehst.\n- In einem schmalen Chat verteilt die Freigabekarte mit **Für diesen Chat zulassen** und **Mit Notiz ablehnen…** ihre Schaltflächen auf mehrere Zeilen. Jede Beschriftung bleibt einzeilig in ihrer Schaltfläche.",
|
|
11
|
+
"ru": "## Новое\n\n- **Мобильное подключение (iPhone)**: уведомления Tether приходят и тогда, когда Tether закрыт. Положите ключ APNs в `apns.json` на Mac (см. `tether/README.md`). После этого Mac отправляет уведомления об одобрении, вопросе, ошибке и Turn finished через push-сервис Apple. Mac должен быть активен, а dsh открыт. Каждое уведомление показывает Mac и проект чата (`Studio Mac · recipe-box`), а также название чата. Turn finished показывает последний ответ агента. Для этого нужна сборка Tether из этой версии.\n\n## Исправления\n\n- **Ввод секрета**: карточка запроса секрета больше не остаётся внутри свёрнутой группы шагов. В режиме настольного приложения карточка раскрывает свою группу, и вы сразу её видите.\n- В узком чате карточка одобрения с кнопками **Разрешить для этого чата** и **Отклонить с примечанием…** располагает кнопки в несколько строк. Надпись каждой кнопки остаётся в одной строке внутри кнопки.",
|
|
12
|
+
"ja": "## 新機能\n\n- **モバイル接続(iPhone)**:Tether を閉じていても Tether の通知が届くようになりました。Mac の `apns.json` に APNs キーを置いてください(`tether/README.md` を参照)。すると Mac は、承認、質問、エラー、Turn finished の通知を Apple のプッシュサービスで送ります。Mac はスリープしておらず、dsh が開いている必要があります。各通知には Mac とチャットのプロジェクト(`Studio Mac · recipe-box`)、チャットのタイトルが表示されます。Turn finished にはエージェントの最後の返信が表示されます。このバージョンからビルドした Tether が必要です。\n\n## 修正\n\n- **シークレット入力**:シークレット要求のカードが、閉じたステップグループの中に隠れたままになることはなくなりました。デスクトップ表示では、カードが自分のグループを開くので、すぐに見えます。\n- 狭いチャットでは、**このチャットで許可** と **メモを付けて拒否…** がある承認カードのボタンが複数行に並びます。各ボタンの文字はボタンの中で 1 行に収まります。",
|
|
13
|
+
"ko": "## 새 기능\n\n- **모바일 연결(iPhone)**: Tether를 닫아 두어도 Tether 알림을 받을 수 있습니다. Mac의 `apns.json`에 APNs 키를 넣으세요(`tether/README.md` 참고). 그러면 Mac이 승인, 질문, 오류, Turn finished 알림을 Apple 푸시 서비스로 보냅니다. Mac은 깨어 있고 dsh가 열려 있어야 합니다. 각 알림에는 Mac과 채팅의 프로젝트(`Studio Mac · recipe-box`), 채팅 제목이 표시됩니다. Turn finished에는 에이전트의 마지막 답변이 표시됩니다. 이 버전으로 빌드한 Tether가 필요합니다.\n\n## 수정 사항\n\n- **비밀 값 입력**: 비밀 값 요청 카드가 더 이상 닫힌 단계 그룹 안에 남아 있지 않습니다. 데스크톱 보기에서 카드가 자기 그룹을 열어 바로 보입니다.\n- 좁은 채팅에서 **이 채팅에서 허용**과 **메모와 함께 거부…**가 있는 승인 카드는 버튼을 여러 줄에 배치합니다. 각 버튼의 글자는 버튼 안에서 한 줄로 유지됩니다.",
|
|
14
|
+
"id": "## Baru\n\n- **Koneksi seluler (iPhone)**: notifikasi Tether kini bisa datang juga saat Tether tertutup. Taruh kunci APNs di `apns.json` pada Mac (lihat `tether/README.md`). Lalu Mac mengirim notifikasi persetujuan, pertanyaan, galat, dan Turn finished lewat layanan push Apple. Mac harus menyala (tidak tidur) dengan dsh terbuka. Setiap notifikasi menampilkan Mac dan proyek obrolan (`Studio Mac · recipe-box`) serta judul obrolan. Turn finished menampilkan balasan terakhir agen. Ini memerlukan build Tether dari versi ini.\n\n## Perbaikan\n\n- **Input rahasia**: kartu permintaan rahasia tidak lagi tetap berada di dalam grup langkah yang tertutup. Di tampilan desktop, kartu membuka grupnya sendiri sehingga Anda langsung melihatnya.\n- Di obrolan yang sempit, kartu persetujuan dengan **Izinkan untuk obrolan ini** dan **Tolak dengan catatan…** menyusun tombolnya dalam beberapa baris. Teks setiap tombol tetap satu baris di dalam tombolnya.",
|
|
15
|
+
"vi": "## Mới\n\n- **Kết nối di động (iPhone)**: thông báo của Tether giờ có thể đến cả khi Tether đã đóng. Đặt một khóa APNs vào `apns.json` trên Mac (xem `tether/README.md`). Khi đó Mac gửi các thông báo phê duyệt, câu hỏi, lỗi và Turn finished qua dịch vụ push của Apple. Mac phải đang thức và dsh phải đang mở. Mỗi thông báo hiển thị Mac và dự án của cuộc trò chuyện (`Studio Mac · recipe-box`) cùng tiêu đề cuộc trò chuyện. Turn finished hiển thị câu trả lời cuối cùng của agent. Cần một bản build Tether từ phiên bản này.\n\n## Sửa lỗi\n\n- **Nhập giá trị bí mật**: thẻ của một yêu cầu bí mật không còn nằm trong một nhóm bước đã thu gọn. Ở chế độ xem desktop, thẻ tự mở nhóm của nó nên bạn thấy thẻ ngay.\n- Trong một cuộc trò chuyện hẹp, thẻ phê duyệt có **Cho phép trong cuộc trò chuyện này** và **Từ chối kèm ghi chú…** xếp các nút thành nhiều dòng. Chữ của mỗi nút nằm trên một dòng trong nút của nó.",
|
|
16
|
+
"hi": "## नया\n\n- **मोबाइल कनेक्ट (iPhone)**: Tether बंद होने पर भी अब Tether सूचनाएँ आ सकती हैं। Mac पर `apns.json` में एक APNs कुंजी रखें (`tether/README.md` देखें)। इसके बाद Mac अनुमति, प्रश्न, त्रुटि और Turn finished सूचनाएँ Apple की पुश सेवा से भेजता है। Mac जागा हुआ होना चाहिए और dsh खुला होना चाहिए। हर सूचना Mac और चैट का प्रोजेक्ट (`Studio Mac · recipe-box`) और चैट का शीर्षक दिखाती है। Turn finished एजेंट का आखिरी जवाब दिखाता है। इसके लिए इस वर्शन से बना Tether बिल्ड चाहिए।\n\n## सुधार\n\n- **सीक्रेट इनपुट**: सीक्रेट अनुरोध का कार्ड अब किसी बंद स्टेप समूह के अंदर नहीं रहता। डेस्कटॉप व्यू में कार्ड अपना समूह खोल देता है, इसलिए आप उसे तुरंत देखते हैं।\n- संकरी चैट में, **इस चैट के लिए अनुमति दें** और **नोट के साथ अस्वीकार करें…** वाला अनुमति कार्ड अपने बटन कई पंक्तियों में रखता है। हर बटन का लेबल बटन के अंदर एक ही पंक्ति में रहता है।"
|
|
17
17
|
}
|
|
18
18
|
}
|