@try-works/dsh-righthand 0.1.3 → 0.1.5
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.md +6 -4
- package/lib/index.d.ts +5 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +7 -1
- package/lib/index.js.map +1 -1
- package/lib/task-tools.d.ts +44 -0
- package/lib/task-tools.d.ts.map +1 -0
- package/lib/task-tools.js +212 -0
- package/lib/task-tools.js.map +1 -0
- package/lib/text-tools.d.ts +18 -0
- package/lib/text-tools.d.ts.map +1 -0
- package/lib/text-tools.js +158 -0
- package/lib/text-tools.js.map +1 -0
- package/package.json +1 -1
- package/skills/dsh-righthand/SKILL.md +18 -2
- package/src/index.ts +7 -1
- package/src/task-tools.ts +236 -0
- package/src/text-tools.ts +173 -0
package/README.md
CHANGED
|
@@ -25,6 +25,8 @@ dsh --profile <profile> --patch ./cordis.patch.yml
|
|
|
25
25
|
| Tool | Family | Backing service |
|
|
26
26
|
|---|---|---|
|
|
27
27
|
| `rh_store_put` / `rh_store_get` / `rh_store_delete` / `rh_store_list` | store | `ctx.storageDomain` (domain KV) |
|
|
28
|
+
| `rh_task_create` / `rh_task_list` / `rh_task_next` / `rh_task_update` / `rh_task_delete` | tasks | `ctx.storageDomain` (typed `righthand_tasks` domain; state machine open → done/failed) |
|
|
29
|
+
| `rh_text_summarise` / `rh_text_extract` / `rh_text_classify` / `rh_text_translate` | text | `ctx.llm` (one model call per verb) |
|
|
28
30
|
| `rh_credential_describe` / `rh_credential_set` / `rh_credential_unset` | secrets | `ctx.credentials` (values never echoed) |
|
|
29
31
|
| `rh_settings_get` / `rh_settings_set` | secrets | `ctx.settings` (namespace `righthand`) |
|
|
30
32
|
| `rh_run` | exec | `ctx.subprocess` (collect mode, bounded output) |
|
|
@@ -137,10 +139,10 @@ so the agent cannot deploy or destroy without the gate.
|
|
|
137
139
|
|
|
138
140
|
## Service availability
|
|
139
141
|
|
|
140
|
-
The store
|
|
141
|
-
(`storage` + `storage-json` + `storage-domain` rows). In a profile
|
|
142
|
-
it — e.g. the headless bundle —
|
|
143
|
-
seven tools still register; the plugin never fails the boot.
|
|
142
|
+
The store and task families need `ctx.storageDomain`, which the web profile
|
|
143
|
+
provides (`storage` + `storage-json` + `storage-domain` rows). In a profile
|
|
144
|
+
without it — e.g. the headless bundle — those nine tools stay dormant and the
|
|
145
|
+
other seven tools still register; the plugin never fails the boot.
|
|
144
146
|
|
|
145
147
|
## Development
|
|
146
148
|
|
package/lib/index.d.ts
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* Modules (each individually mountable via `export * as ...`):
|
|
15
15
|
* - store-tools: rh_store_put/get/delete/list over ctx.storageDomain
|
|
16
16
|
* - secrets-tools: rh_credential_describe/set/unset + rh_settings_get/set
|
|
17
|
+
* - task-tools: rh_task_create/list/next/update/delete over ctx.storageDomain
|
|
18
|
+
* - text-tools: rh_text_summarise/extract/classify/translate over ctx.llm
|
|
17
19
|
* - exec-tools: rh_run / rh_run_bg over ctx.subprocess + ctx.jobs
|
|
18
20
|
* - guard-tools: tools/pre-execute policy (config.rules)
|
|
19
21
|
* - skills: the packaged dsh-righthand skill (ctx.skills)
|
|
@@ -25,6 +27,8 @@ import * as storeTools from './store-tools.ts';
|
|
|
25
27
|
import * as secretsTools from './secrets-tools.ts';
|
|
26
28
|
import * as execTools from './exec-tools.ts';
|
|
27
29
|
import * as guardTools from './guard-tools.ts';
|
|
30
|
+
import * as taskTools from './task-tools.ts';
|
|
31
|
+
import * as textTools from './text-tools.ts';
|
|
28
32
|
import * as righthandSkills from './skills.ts';
|
|
29
33
|
import type { GuardRule } from './guard-tools.ts';
|
|
30
34
|
export declare const name = "dsh-righthand";
|
|
@@ -42,7 +46,7 @@ export interface RighthandConfig {
|
|
|
42
46
|
* instead of failing the whole plugin.
|
|
43
47
|
*/
|
|
44
48
|
export declare function apply(ctx: Context, config?: RighthandConfig): void;
|
|
45
|
-
export { storeTools, secretsTools, execTools, guardTools, righthandSkills };
|
|
49
|
+
export { storeTools, secretsTools, execTools, taskTools, textTools, guardTools, righthandSkills };
|
|
46
50
|
export { SKILL_NAME, SKILL_DESCRIPTION, skillDirectory, skillBody } from './skills.ts';
|
|
47
51
|
export type { GuardRule } from './guard-tools.ts';
|
|
48
52
|
export { guardFactsFor } from './guard-tools.ts';
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAA;AAClD,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAA;AAC5C,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAA;AAC5C,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAA;AAC5C,OAAO,KAAK,eAAe,MAAM,aAAa,CAAA;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAEjD,eAAO,MAAM,IAAI,kBAAkB,CAAA;AAEnC,iFAAiF;AACjF,eAAO,MAAM,MAAM,EAAE,MAAM,EAAO,CAAA;AAElC,qCAAqC;AACrC,MAAM,WAAW,eAAe;IAC9B,qEAAqE;IACrE,KAAK,CAAC,EAAE,SAAS,EAAE,CAAA;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,eAAoB,GAAG,IAAI,CAQtE;AAGD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,CAAA;AACjG,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACtF,YAAY,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAChD,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA"}
|
package/lib/index.js
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* Modules (each individually mountable via `export * as ...`):
|
|
15
15
|
* - store-tools: rh_store_put/get/delete/list over ctx.storageDomain
|
|
16
16
|
* - secrets-tools: rh_credential_describe/set/unset + rh_settings_get/set
|
|
17
|
+
* - task-tools: rh_task_create/list/next/update/delete over ctx.storageDomain
|
|
18
|
+
* - text-tools: rh_text_summarise/extract/classify/translate over ctx.llm
|
|
17
19
|
* - exec-tools: rh_run / rh_run_bg over ctx.subprocess + ctx.jobs
|
|
18
20
|
* - guard-tools: tools/pre-execute policy (config.rules)
|
|
19
21
|
* - skills: the packaged dsh-righthand skill (ctx.skills)
|
|
@@ -24,6 +26,8 @@ import * as storeTools from "./store-tools.js";
|
|
|
24
26
|
import * as secretsTools from "./secrets-tools.js";
|
|
25
27
|
import * as execTools from "./exec-tools.js";
|
|
26
28
|
import * as guardTools from "./guard-tools.js";
|
|
29
|
+
import * as taskTools from "./task-tools.js";
|
|
30
|
+
import * as textTools from "./text-tools.js";
|
|
27
31
|
import * as righthandSkills from "./skills.js";
|
|
28
32
|
export const name = 'dsh-righthand';
|
|
29
33
|
/** The combined plugin needs no services itself; each child waits on its own. */
|
|
@@ -38,11 +42,13 @@ export function apply(ctx, config = {}) {
|
|
|
38
42
|
ctx.plugin(storeTools);
|
|
39
43
|
ctx.plugin(secretsTools);
|
|
40
44
|
ctx.plugin(execTools);
|
|
45
|
+
ctx.plugin(taskTools);
|
|
46
|
+
ctx.plugin(textTools);
|
|
41
47
|
ctx.plugin(guardTools, { rules: config.rules ?? [] });
|
|
42
48
|
ctx.plugin(righthandSkills);
|
|
43
49
|
}
|
|
44
50
|
// Individual modules stay importable for selective mounting.
|
|
45
|
-
export { storeTools, secretsTools, execTools, guardTools, righthandSkills };
|
|
51
|
+
export { storeTools, secretsTools, execTools, taskTools, textTools, guardTools, righthandSkills };
|
|
46
52
|
export { SKILL_NAME, SKILL_DESCRIPTION, skillDirectory, skillBody } from "./skills.js";
|
|
47
53
|
export { guardFactsFor } from "./guard-tools.js";
|
|
48
54
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAA;AAClD,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAA;AAC5C,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAA;AAC5C,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAA;AAC5C,OAAO,KAAK,eAAe,MAAM,aAAa,CAAA;AAG9C,MAAM,CAAC,MAAM,IAAI,GAAG,eAAe,CAAA;AAEnC,iFAAiF;AACjF,MAAM,CAAC,MAAM,MAAM,GAAa,EAAE,CAAA;AAQlC;;;;;GAKG;AACH,MAAM,UAAU,KAAK,CAAC,GAAY,EAAE,SAA0B,EAAE;IAC9D,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;IACtB,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;IACxB,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IACrB,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IACrB,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IACrB,GAAG,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC,CAAA;IACrD,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,CAAA;AAC7B,CAAC;AAED,6DAA6D;AAC7D,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,CAAA;AACjG,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAEtF,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* task-tools — rh_task_* over ctx.storageDomain.
|
|
3
|
+
* A typed task board with a state machine: open → done | failed.
|
|
4
|
+
* `rh_task_next` is the queue discipline — the oldest open task is what to
|
|
5
|
+
* work on now. Descriptions carry the rule that a task's failure is recorded
|
|
6
|
+
* like an answer: a run that could not happen is news the owner needs.
|
|
7
|
+
* @module dsh-righthand/task-tools
|
|
8
|
+
*/
|
|
9
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
10
|
+
import type { Domain } from '@deepseek-ai/dsh-storage-domain';
|
|
11
|
+
export declare const name = "righthand-task";
|
|
12
|
+
export declare const inject: string[];
|
|
13
|
+
export type TaskState = 'open' | 'done' | 'failed';
|
|
14
|
+
export interface Task {
|
|
15
|
+
id: string;
|
|
16
|
+
title: string;
|
|
17
|
+
detail: string;
|
|
18
|
+
due: string;
|
|
19
|
+
state: TaskState;
|
|
20
|
+
result: string;
|
|
21
|
+
createdAt: string;
|
|
22
|
+
updatedAt: string;
|
|
23
|
+
}
|
|
24
|
+
/** Domain spec: the typed task table. */
|
|
25
|
+
export declare const taskDomain: {
|
|
26
|
+
name: string;
|
|
27
|
+
version: number;
|
|
28
|
+
tables: {
|
|
29
|
+
tasks: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<string, {
|
|
30
|
+
id: string;
|
|
31
|
+
title: string;
|
|
32
|
+
detail: string;
|
|
33
|
+
due: string;
|
|
34
|
+
state: "failed" | "open" | "done";
|
|
35
|
+
result: string;
|
|
36
|
+
createdAt: string;
|
|
37
|
+
updatedAt: string;
|
|
38
|
+
}>;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
export type TaskDomain = Domain<typeof taskDomain>;
|
|
42
|
+
/** Open the task domain and register the model-facing tools. */
|
|
43
|
+
export declare function apply(ctx: Context): void;
|
|
44
|
+
//# sourceMappingURL=task-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-tools.d.ts","sourceRoot":"","sources":["../src/task-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAGlD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iCAAiC,CAAA;AAG7D,eAAO,MAAM,IAAI,mBAAmB,CAAA;AACpC,eAAO,MAAM,MAAM,UAA6B,CAAA;AAEhD,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAA;AAElD,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,SAAS,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,yCAAyC;AACzC,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;CAerB,CAAA;AAEF,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAA;AAMlD,gEAAgE;AAChE,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAgLxC"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* task-tools — rh_task_* over ctx.storageDomain.
|
|
3
|
+
* A typed task board with a state machine: open → done | failed.
|
|
4
|
+
* `rh_task_next` is the queue discipline — the oldest open task is what to
|
|
5
|
+
* work on now. Descriptions carry the rule that a task's failure is recorded
|
|
6
|
+
* like an answer: a run that could not happen is news the owner needs.
|
|
7
|
+
* @module dsh-righthand/task-tools
|
|
8
|
+
*/
|
|
9
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
10
|
+
import { defineDomain, domainTable } from '@deepseek-ai/dsh-storage-domain';
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
export const name = 'righthand-task';
|
|
13
|
+
export const inject = ['tools', 'storageDomain'];
|
|
14
|
+
/** Domain spec: the typed task table. */
|
|
15
|
+
export const taskDomain = defineDomain({
|
|
16
|
+
name: 'righthand_tasks',
|
|
17
|
+
version: 1,
|
|
18
|
+
tables: {
|
|
19
|
+
tasks: domainTable(z.object({
|
|
20
|
+
id: z.string(),
|
|
21
|
+
title: z.string(),
|
|
22
|
+
detail: z.string(),
|
|
23
|
+
due: z.string(),
|
|
24
|
+
state: z.enum(['open', 'done', 'failed']),
|
|
25
|
+
result: z.string(),
|
|
26
|
+
createdAt: z.string(),
|
|
27
|
+
updatedAt: z.string(),
|
|
28
|
+
})),
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
function newId() {
|
|
32
|
+
return 't-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 6);
|
|
33
|
+
}
|
|
34
|
+
/** Open the task domain and register the model-facing tools. */
|
|
35
|
+
export function apply(ctx) {
|
|
36
|
+
let domain;
|
|
37
|
+
let ready;
|
|
38
|
+
ctx.effect(() => {
|
|
39
|
+
ready = (async () => {
|
|
40
|
+
domain = await ctx.storageDomain.open(taskDomain);
|
|
41
|
+
})();
|
|
42
|
+
return () => { };
|
|
43
|
+
});
|
|
44
|
+
const ensure = async () => {
|
|
45
|
+
if (domain === undefined) {
|
|
46
|
+
await ready;
|
|
47
|
+
}
|
|
48
|
+
if (domain === undefined)
|
|
49
|
+
throw new Error('righthand tasks are not open (storageDomain not mounted)');
|
|
50
|
+
return domain;
|
|
51
|
+
};
|
|
52
|
+
const all = async () => {
|
|
53
|
+
const d = await ensure();
|
|
54
|
+
return [...d.table('tasks').entries()].map(([, t]) => t);
|
|
55
|
+
};
|
|
56
|
+
ctx.tools.register(defineTool({
|
|
57
|
+
name: 'rh_task_create',
|
|
58
|
+
description: 'Add a task with a title, optional detail and due date. Returns the created task with a generated id and state open. A task the agent cannot run is still recorded — its failure is delivered like an answer.',
|
|
59
|
+
parameters: {
|
|
60
|
+
title: { type: 'string', required: true, description: 'What the task is, e.g. "deploy vision-worker".' },
|
|
61
|
+
detail: { type: 'string', description: 'Optional longer description of what done means.' },
|
|
62
|
+
due: { type: 'string', description: 'Optional due date, YYYY-MM-DD.' },
|
|
63
|
+
},
|
|
64
|
+
output: {
|
|
65
|
+
schema: {
|
|
66
|
+
type: 'object',
|
|
67
|
+
additionalProperties: false,
|
|
68
|
+
properties: {
|
|
69
|
+
id: { type: 'string', required: true },
|
|
70
|
+
title: { type: 'string', required: true },
|
|
71
|
+
state: { type: 'string', required: true },
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
render: (_args, value) => [{ type: 'text', text: 'task ' + value.id + ' created: ' + value.title + ' (open)' }],
|
|
75
|
+
},
|
|
76
|
+
async execute(args) {
|
|
77
|
+
const d = await ensure();
|
|
78
|
+
const now = new Date().toISOString();
|
|
79
|
+
const task = {
|
|
80
|
+
id: newId(),
|
|
81
|
+
title: args.title.trim(),
|
|
82
|
+
detail: args.detail ?? '',
|
|
83
|
+
due: args.due ?? '',
|
|
84
|
+
state: 'open',
|
|
85
|
+
result: '',
|
|
86
|
+
createdAt: now,
|
|
87
|
+
updatedAt: now,
|
|
88
|
+
};
|
|
89
|
+
if (task.title === '')
|
|
90
|
+
throw new Error('a task needs a title');
|
|
91
|
+
await d.table('tasks').put(task.id, task);
|
|
92
|
+
return { id: task.id, title: task.title, state: task.state };
|
|
93
|
+
},
|
|
94
|
+
}));
|
|
95
|
+
ctx.tools.register(defineTool({
|
|
96
|
+
name: 'rh_task_list',
|
|
97
|
+
description: 'List tasks, open ones first (oldest open first). Optionally filter by state.',
|
|
98
|
+
parameters: {
|
|
99
|
+
state: { type: 'string', description: 'Filter: open | done | failed. Omit for all tasks.' },
|
|
100
|
+
},
|
|
101
|
+
output: {
|
|
102
|
+
schema: {
|
|
103
|
+
type: 'array',
|
|
104
|
+
items: {
|
|
105
|
+
type: 'object',
|
|
106
|
+
additionalProperties: false,
|
|
107
|
+
properties: {
|
|
108
|
+
id: { type: 'string', required: true },
|
|
109
|
+
title: { type: 'string', required: true },
|
|
110
|
+
state: { type: 'string', required: true },
|
|
111
|
+
due: { type: 'string' },
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
render: (_args, tasks) => [{ type: 'text', text: tasks.length === 0 ? '(no tasks)' : tasks.map((t) => '[' + t.state + '] ' + t.title).join(' / ') }],
|
|
116
|
+
},
|
|
117
|
+
async execute(args) {
|
|
118
|
+
const tasks = await all();
|
|
119
|
+
const wanted = args.state ?? null;
|
|
120
|
+
const filtered = wanted ? tasks.filter(t => t.state === wanted) : tasks;
|
|
121
|
+
const rank = { open: 0, done: 1, failed: 2 };
|
|
122
|
+
filtered.sort((a, b) => (rank[a.state] - rank[b.state]) || a.createdAt.localeCompare(b.createdAt));
|
|
123
|
+
return filtered.map(t => ({ id: t.id, title: t.title, state: t.state, ...t.due ? { due: t.due } : {} }));
|
|
124
|
+
},
|
|
125
|
+
}));
|
|
126
|
+
ctx.tools.register(defineTool({
|
|
127
|
+
name: 'rh_task_next',
|
|
128
|
+
description: 'The next open task — what to work on now. Oldest open task first. Returns found: false when nothing is open.',
|
|
129
|
+
parameters: {},
|
|
130
|
+
output: {
|
|
131
|
+
schema: {
|
|
132
|
+
type: 'object',
|
|
133
|
+
additionalProperties: false,
|
|
134
|
+
properties: {
|
|
135
|
+
found: { type: 'boolean', required: true },
|
|
136
|
+
id: { type: 'string' },
|
|
137
|
+
title: { type: 'string' },
|
|
138
|
+
detail: { type: 'string' },
|
|
139
|
+
due: { type: 'string' },
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
render: (_args, value) => [{ type: 'text', text: value.found ? 'next: ' + value.title : '(nothing open)' }],
|
|
143
|
+
},
|
|
144
|
+
async execute() {
|
|
145
|
+
const tasks = await all();
|
|
146
|
+
const open = tasks.filter(t => t.state === 'open').sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
147
|
+
const t = open[0];
|
|
148
|
+
if (t === undefined)
|
|
149
|
+
return { found: false };
|
|
150
|
+
return { found: true, id: t.id, title: t.title, ...t.detail ? { detail: t.detail } : {}, ...t.due ? { due: t.due } : {} };
|
|
151
|
+
},
|
|
152
|
+
}));
|
|
153
|
+
ctx.tools.register(defineTool({
|
|
154
|
+
name: 'rh_task_update',
|
|
155
|
+
description: 'Change a task: its state (open | done | failed) and/or its result. A failed task should carry its result — what was tried and what went wrong — so the record says what happened, not just that something did.',
|
|
156
|
+
parameters: {
|
|
157
|
+
id: { type: 'string', required: true, description: 'The task id from rh_task_create or rh_task_list.' },
|
|
158
|
+
state: { type: 'string', description: 'New state: open | done | failed.' },
|
|
159
|
+
result: { type: 'string', description: 'What happened — the outcome, or the failure reason.' },
|
|
160
|
+
},
|
|
161
|
+
output: {
|
|
162
|
+
schema: {
|
|
163
|
+
type: 'object',
|
|
164
|
+
additionalProperties: false,
|
|
165
|
+
properties: {
|
|
166
|
+
found: { type: 'boolean', required: true },
|
|
167
|
+
id: { type: 'string', required: true },
|
|
168
|
+
state: { type: 'string', required: true },
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
render: (_args, value) => [{ type: 'text', text: value.found ? 'task ' + value.id + ' -> ' + value.state : 'task ' + value.id + ' not found' }],
|
|
172
|
+
},
|
|
173
|
+
async execute(args) {
|
|
174
|
+
const d = await ensure();
|
|
175
|
+
const task = d.table('tasks').get(args.id);
|
|
176
|
+
if (task === undefined)
|
|
177
|
+
return { found: false, id: args.id, state: 'open' };
|
|
178
|
+
const next = {
|
|
179
|
+
...task,
|
|
180
|
+
...args.state !== undefined ? { state: args.state } : {},
|
|
181
|
+
...args.result !== undefined ? { result: args.result } : {},
|
|
182
|
+
updatedAt: new Date().toISOString(),
|
|
183
|
+
};
|
|
184
|
+
await d.table('tasks').put(args.id, next);
|
|
185
|
+
return { found: true, id: args.id, state: next.state };
|
|
186
|
+
},
|
|
187
|
+
}));
|
|
188
|
+
ctx.tools.register(defineTool({
|
|
189
|
+
name: 'rh_task_delete',
|
|
190
|
+
description: 'Remove a task by id. Returns whether it existed.',
|
|
191
|
+
parameters: {
|
|
192
|
+
id: { type: 'string', required: true, description: 'The task id to remove.' },
|
|
193
|
+
},
|
|
194
|
+
output: {
|
|
195
|
+
schema: {
|
|
196
|
+
type: 'object',
|
|
197
|
+
additionalProperties: false,
|
|
198
|
+
properties: {
|
|
199
|
+
id: { type: 'string', required: true },
|
|
200
|
+
existed: { type: 'boolean', required: true },
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
render: (_args, value) => [{ type: 'text', text: 'deleted ' + value.id + ': ' + value.existed }],
|
|
204
|
+
},
|
|
205
|
+
async execute(args) {
|
|
206
|
+
const d = await ensure();
|
|
207
|
+
const existed = await d.table('tasks').delete(args.id);
|
|
208
|
+
return { id: args.id, existed };
|
|
209
|
+
},
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=task-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-tools.js","sourceRoot":"","sources":["../src/task-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* text-tools — rh_text_* over ctx.llm.
|
|
3
|
+
* Language work at one call per verb: summarise, extract-to-JSON,
|
|
4
|
+
* classify, translate. Same prompt discipline as the digest summarizer:
|
|
5
|
+
* roles stay roles, caps are deliberate, each call's failure is contained
|
|
6
|
+
* in that call (the other tools are unaffected).
|
|
7
|
+
* @module dsh-righthand/text-tools
|
|
8
|
+
*/
|
|
9
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
10
|
+
export declare const name = "righthand-text";
|
|
11
|
+
export declare const inject: string[];
|
|
12
|
+
export interface TextConfig {
|
|
13
|
+
/** LLM route; defaults match the harness default model. */
|
|
14
|
+
provider?: string;
|
|
15
|
+
model?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function apply(ctx: Context, config?: TextConfig): void;
|
|
18
|
+
//# sourceMappingURL=text-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-tools.d.ts","sourceRoot":"","sources":["../src/text-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAKlD,eAAO,MAAM,IAAI,mBAAmB,CAAA;AACpC,eAAO,MAAM,MAAM,UAAmB,CAAA;AAEtC,MAAM,WAAW,UAAU;IACzB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AA8CD,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,UAAe,GAAG,IAAI,CAoGjE"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* text-tools — rh_text_* over ctx.llm.
|
|
3
|
+
* Language work at one call per verb: summarise, extract-to-JSON,
|
|
4
|
+
* classify, translate. Same prompt discipline as the digest summarizer:
|
|
5
|
+
* roles stay roles, caps are deliberate, each call's failure is contained
|
|
6
|
+
* in that call (the other tools are unaffected).
|
|
7
|
+
* @module dsh-righthand/text-tools
|
|
8
|
+
*/
|
|
9
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
10
|
+
import { BlockAssembler, createUserMessage, deepFreeze } from '@deepseek-ai/dsh-llm';
|
|
11
|
+
export const name = 'righthand-text';
|
|
12
|
+
export const inject = ['tools', 'llm'];
|
|
13
|
+
const DEFAULTS = { provider: 'deepseek-official', model: 'deepseek-v4-flash' };
|
|
14
|
+
/** One LLM call, text in, text out. Returns the assembled text or throws. */
|
|
15
|
+
async function ask(ctx, cfg, system, user, maxTokens, signal) {
|
|
16
|
+
const options = deepFreeze({
|
|
17
|
+
provider: cfg.provider,
|
|
18
|
+
model: cfg.model,
|
|
19
|
+
messages: [createUserMessage({
|
|
20
|
+
content: [{ type: 'text', text: user }],
|
|
21
|
+
source: { kind: 'plugin', plugin: 'righthand-text' },
|
|
22
|
+
})],
|
|
23
|
+
system,
|
|
24
|
+
maxTokens,
|
|
25
|
+
purpose: 'compaction',
|
|
26
|
+
signal,
|
|
27
|
+
});
|
|
28
|
+
const assembler = new BlockAssembler();
|
|
29
|
+
for await (const chunk of ctx.llm.stream(options))
|
|
30
|
+
assembler.push(chunk);
|
|
31
|
+
if (assembler.finish.kind !== 'stop')
|
|
32
|
+
throw new Error('text model call did not stop cleanly: ' + JSON.stringify(assembler.finish.kind));
|
|
33
|
+
const text = assembler.blocks().filter((b) => b.type === 'text').map((b) => b.text).join(' ').trim();
|
|
34
|
+
if (text === '')
|
|
35
|
+
throw new Error('text model call produced no text');
|
|
36
|
+
return text;
|
|
37
|
+
}
|
|
38
|
+
/** Extract the outermost {...} JSON object from a model reply. */
|
|
39
|
+
function parseJsonWindow(text) {
|
|
40
|
+
const start = text.indexOf('{');
|
|
41
|
+
const end = text.lastIndexOf('}');
|
|
42
|
+
if (start < 0 || end <= start)
|
|
43
|
+
throw new Error('model output was not JSON');
|
|
44
|
+
return JSON.parse(text.slice(start, end + 1));
|
|
45
|
+
}
|
|
46
|
+
/** Summarise: plain prose, no JSON. */
|
|
47
|
+
const SUMMARISE_SYSTEM = 'You summarise text. Return ONLY the summary itself - a few sentences, plain prose, no JSON, no Markdown headers, no quotes around it. Preserve the language of the input.';
|
|
48
|
+
/** Extract: JSON matching a caller schema. */
|
|
49
|
+
const EXTRACT_SYSTEM = 'You extract structured data from text. Return ONLY one JSON object matching the caller schema exactly - no prose, no Markdown, no code fences.';
|
|
50
|
+
/** Classify: one label plus confidence, as JSON. */
|
|
51
|
+
const CLASSIFY_SYSTEM = 'You classify text. Return ONLY one JSON object with exactly two keys: label (one of the given labels, verbatim) and confidence (a number from 0 to 1). No prose, no Markdown.';
|
|
52
|
+
/** Translate: preserve formatting, plain prose. */
|
|
53
|
+
const TRANSLATE_SYSTEM = 'You translate text. Return ONLY the translation - plain prose, no JSON, no quotes around it, preserving paragraph breaks and any Markdown or code the input contains.';
|
|
54
|
+
export function apply(ctx, config = {}) {
|
|
55
|
+
const cfg = { ...DEFAULTS, ...config };
|
|
56
|
+
ctx.tools.register(defineTool({
|
|
57
|
+
name: 'rh_text_summarise',
|
|
58
|
+
description: 'Summarise text into a few sentences. One model call per invocation; the result is new prose, not extracted sentences.',
|
|
59
|
+
parameters: {
|
|
60
|
+
text: { type: 'string', required: true, description: 'The text to summarise (max ~30000 characters).' },
|
|
61
|
+
sentences: { type: 'integer', description: 'Target length in sentences (default 3).' },
|
|
62
|
+
},
|
|
63
|
+
output: {
|
|
64
|
+
schema: {
|
|
65
|
+
type: 'object',
|
|
66
|
+
additionalProperties: false,
|
|
67
|
+
properties: {
|
|
68
|
+
summary: { type: 'string', required: true },
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
render: (_args, value) => [{ type: 'text', text: value.summary }],
|
|
72
|
+
},
|
|
73
|
+
async execute(args, exec) {
|
|
74
|
+
const n = args.sentences ?? 3;
|
|
75
|
+
const summary = await ask(ctx, cfg, SUMMARISE_SYSTEM, 'Summarise this in about ' + n + ' sentences:\n\n' + args.text, 600, exec.signal);
|
|
76
|
+
return { summary };
|
|
77
|
+
},
|
|
78
|
+
}));
|
|
79
|
+
ctx.tools.register(defineTool({
|
|
80
|
+
name: 'rh_text_extract',
|
|
81
|
+
description: 'Turn text into JSON matching a schema you give. The schema is a JSON Schema object; the model returns one object conforming to it, or the call fails cleanly.',
|
|
82
|
+
parameters: {
|
|
83
|
+
text: { type: 'string', required: true, description: 'The text to extract from.' },
|
|
84
|
+
schema: { type: 'json', required: true, description: 'JSON Schema the result must match, e.g. { "type": "object", "properties": { "name": { "type": "string" } }, "required": ["name"] }.' },
|
|
85
|
+
},
|
|
86
|
+
output: {
|
|
87
|
+
schema: {
|
|
88
|
+
type: 'object',
|
|
89
|
+
additionalProperties: false,
|
|
90
|
+
properties: {
|
|
91
|
+
extracted: { type: 'json', required: true },
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
render: (_args, value) => [{ type: 'text', text: JSON.stringify(value.extracted) }],
|
|
95
|
+
},
|
|
96
|
+
async execute(args, exec) {
|
|
97
|
+
const framed = 'Schema:\n' + JSON.stringify(args.schema) + '\n\nText:\n' + args.text;
|
|
98
|
+
const raw = await ask(ctx, cfg, EXTRACT_SYSTEM, framed, 800, exec.signal);
|
|
99
|
+
return { extracted: parseJsonWindow(raw) };
|
|
100
|
+
},
|
|
101
|
+
}));
|
|
102
|
+
ctx.tools.register(defineTool({
|
|
103
|
+
name: 'rh_text_classify',
|
|
104
|
+
description: 'Sort text into one of the labels you give, with a confidence score from 0 to 1. The label returned is one of yours, verbatim.',
|
|
105
|
+
parameters: {
|
|
106
|
+
text: { type: 'string', required: true, description: 'The text to classify.' },
|
|
107
|
+
labels: { type: 'array', required: true, items: { type: 'string' }, description: 'The candidate labels, e.g. ["bug", "feature request", "question"].' },
|
|
108
|
+
},
|
|
109
|
+
output: {
|
|
110
|
+
schema: {
|
|
111
|
+
type: 'object',
|
|
112
|
+
additionalProperties: false,
|
|
113
|
+
properties: {
|
|
114
|
+
label: { type: 'string', required: true },
|
|
115
|
+
confidence: { type: 'number', required: true },
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
render: (_args, value) => [{ type: 'text', text: value.label + ' (' + value.confidence + ')' }],
|
|
119
|
+
},
|
|
120
|
+
async execute(args, exec) {
|
|
121
|
+
if (args.labels.length === 0)
|
|
122
|
+
throw new Error('classify needs at least one label');
|
|
123
|
+
const framed = 'Labels: ' + JSON.stringify(args.labels) + '\n\nText:\n' + args.text;
|
|
124
|
+
const raw = await ask(ctx, cfg, CLASSIFY_SYSTEM, framed, 100, exec.signal);
|
|
125
|
+
const parsed = parseJsonWindow(raw);
|
|
126
|
+
if (typeof parsed.label !== 'string' || !args.labels.includes(parsed.label))
|
|
127
|
+
throw new Error('model returned a label outside the given set');
|
|
128
|
+
const confidence = Number(parsed.confidence);
|
|
129
|
+
if (!Number.isFinite(confidence) || confidence < 0 || confidence > 1)
|
|
130
|
+
throw new Error('model returned an invalid confidence');
|
|
131
|
+
return { label: parsed.label, confidence };
|
|
132
|
+
},
|
|
133
|
+
}));
|
|
134
|
+
ctx.tools.register(defineTool({
|
|
135
|
+
name: 'rh_text_translate',
|
|
136
|
+
description: 'Translate text into another language, preserving formatting (paragraph breaks, Markdown, code).',
|
|
137
|
+
parameters: {
|
|
138
|
+
text: { type: 'string', required: true, description: 'The text to translate.' },
|
|
139
|
+
language: { type: 'string', required: true, description: 'The target language, e.g. "Spanish", "zh-CN", "Japanese".' },
|
|
140
|
+
},
|
|
141
|
+
output: {
|
|
142
|
+
schema: {
|
|
143
|
+
type: 'object',
|
|
144
|
+
additionalProperties: false,
|
|
145
|
+
properties: {
|
|
146
|
+
translation: { type: 'string', required: true },
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
render: (_args, value) => [{ type: 'text', text: value.translation }],
|
|
150
|
+
},
|
|
151
|
+
async execute(args, exec) {
|
|
152
|
+
const framed = 'Translate the following into ' + args.language + ':\n\n' + args.text;
|
|
153
|
+
const translation = await ask(ctx, cfg, TRANSLATE_SYSTEM, framed, 800, exec.signal);
|
|
154
|
+
return { translation };
|
|
155
|
+
},
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=text-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-tools.js","sourceRoot":"","sources":["../src/text-tools.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@try-works/dsh-righthand",
|
|
3
3
|
"description": "DeepSeek Harness plugin: DSH-native righthand tools — durable KV store, credential/settings management, governed exec, and a tool guard",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.5",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/index.d.ts",
|
|
@@ -12,9 +12,11 @@ The righthand toolkit: DSH-native tools over the harness's own services. Use thi
|
|
|
12
12
|
| Family | Tools | Backing service | Use when |
|
|
13
13
|
|--------|-------|-----------------|----------|
|
|
14
14
|
| store | `rh_store_put` / `rh_store_get` / `rh_store_delete` / `rh_store_list` | `ctx.storageDomain` (domain KV) | The task needs durable state across turns: a catalog, counter, cache, or note. Prefer this over files for structured JSON records. |
|
|
15
|
+
| tasks | `rh_task_create` / `rh_task_list` / `rh_task_next` / `rh_task_update` / `rh_task_delete` | `ctx.storageDomain` (typed `righthand_tasks` domain) | A task board with a state machine (open → done/failed). `rh_task_next` is the oldest open task — what to work on now. A failed task's `result` records what went wrong: a task that cannot run is still recorded, never silently dropped. |
|
|
15
16
|
| secrets | `rh_credential_describe` / `rh_credential_set` / `rh_credential_unset` | `ctx.credentials` | The task must check, store, or remove a credential reference (e.g. `CLOUDFLARE_API_TOKEN`). Values are written durably and **never echoed back**. |
|
|
16
17
|
| settings | `rh_settings_get` / `rh_settings_set` | `ctx.settings` (namespace `righthand`) | Reading or patching righthand settings (`accountId`, `defaultZone`, `defaultScriptPrefix`). |
|
|
17
18
|
| exec | `rh_run` / `rh_run_bg` | `ctx.subprocess` + `ctx.jobs` | Running one command (argv array, no shell interpretation). `rh_run` collects bounded output; `rh_run_bg` starts an owner-scoped background job and returns its id. |
|
|
19
|
+
| text | `rh_text_summarise` / `rh_text_extract` / `rh_text_classify` / `rh_text_translate` | `ctx.llm` (one model call per verb) | Language work: shorten, turn into JSON matching a schema, sort into your labels with confidence, or translate preserving formatting. |
|
|
18
20
|
| guard | (policy, not a tool) | `ctx.tools` `tools/pre-execute` | Configured via plugin config `rules`; gates tools by name prefix with `allow` / `deny` / `ask` modes. |
|
|
19
21
|
|
|
20
22
|
## Store semantics
|
|
@@ -23,6 +25,20 @@ The righthand toolkit: DSH-native tools over the harness's own services. Use thi
|
|
|
23
25
|
- `rh_store_get` returns `{ found, key, value?, updatedAt? }` — `found: false` means the key is absent, not an error.
|
|
24
26
|
- Writes are durable (backend flush before commit) and serialized on one write chain; values must be JSON-serializable.
|
|
25
27
|
|
|
28
|
+
## Text semantics
|
|
29
|
+
|
|
30
|
+
- One model call per verb; each call's failure is contained (the other tools are unaffected).
|
|
31
|
+
- `rh_text_extract` takes a JSON Schema and the model returns one object conforming to it — parse failures surface as a clean error, not raw prose.
|
|
32
|
+
- `rh_text_classify` returns a label verbatim from the given set plus a 0–1 confidence; a label outside the set is an error.
|
|
33
|
+
- Provider/model come from plugin config (`provider`/`model`, default the harness default model).
|
|
34
|
+
|
|
35
|
+
## Task semantics
|
|
36
|
+
|
|
37
|
+
- One domain `righthand_tasks`: a `tasks` table keyed by generated task id (`t-...`).
|
|
38
|
+
- States: `open` → `done` | `failed`. `rh_task_next` returns the oldest open task; nothing open returns `{ found: false }`.
|
|
39
|
+
- `rh_task_update` takes a state and/or a `result` — a failed task should carry what was tried and what went wrong, so the record says what happened.
|
|
40
|
+
- `rh_task_list` sorts open first (oldest first), then done, then failed; `state` filters.
|
|
41
|
+
|
|
26
42
|
## Credential semantics
|
|
27
43
|
|
|
28
44
|
- `rh_credential_describe` reports `configured`, `source`, `writable` — never the value.
|
|
@@ -45,5 +61,5 @@ Rules come from the plugin config (`rules: [{ toolPrefix, mode, ask?, destructiv
|
|
|
45
61
|
|
|
46
62
|
## Service availability
|
|
47
63
|
|
|
48
|
-
- **web profile**: all
|
|
49
|
-
- **other profiles** (e.g. headless): the store
|
|
64
|
+
- **web profile**: all twenty tools register (`storageDomain` is provided by the web app rows).
|
|
65
|
+
- **other profiles** (e.g. headless): the store and task families stay dormant; the other seven tools still register. The plugin never fails the boot.
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* Modules (each individually mountable via `export * as ...`):
|
|
15
15
|
* - store-tools: rh_store_put/get/delete/list over ctx.storageDomain
|
|
16
16
|
* - secrets-tools: rh_credential_describe/set/unset + rh_settings_get/set
|
|
17
|
+
* - task-tools: rh_task_create/list/next/update/delete over ctx.storageDomain
|
|
18
|
+
* - text-tools: rh_text_summarise/extract/classify/translate over ctx.llm
|
|
17
19
|
* - exec-tools: rh_run / rh_run_bg over ctx.subprocess + ctx.jobs
|
|
18
20
|
* - guard-tools: tools/pre-execute policy (config.rules)
|
|
19
21
|
* - skills: the packaged dsh-righthand skill (ctx.skills)
|
|
@@ -26,6 +28,8 @@ import * as storeTools from './store-tools.ts'
|
|
|
26
28
|
import * as secretsTools from './secrets-tools.ts'
|
|
27
29
|
import * as execTools from './exec-tools.ts'
|
|
28
30
|
import * as guardTools from './guard-tools.ts'
|
|
31
|
+
import * as taskTools from './task-tools.ts'
|
|
32
|
+
import * as textTools from './text-tools.ts'
|
|
29
33
|
import * as righthandSkills from './skills.ts'
|
|
30
34
|
import type { GuardRule } from './guard-tools.ts'
|
|
31
35
|
|
|
@@ -50,12 +54,14 @@ export function apply(ctx: Context, config: RighthandConfig = {}): void {
|
|
|
50
54
|
ctx.plugin(storeTools)
|
|
51
55
|
ctx.plugin(secretsTools)
|
|
52
56
|
ctx.plugin(execTools)
|
|
57
|
+
ctx.plugin(taskTools)
|
|
58
|
+
ctx.plugin(textTools)
|
|
53
59
|
ctx.plugin(guardTools, { rules: config.rules ?? [] })
|
|
54
60
|
ctx.plugin(righthandSkills)
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
// Individual modules stay importable for selective mounting.
|
|
58
|
-
export { storeTools, secretsTools, execTools, guardTools, righthandSkills }
|
|
64
|
+
export { storeTools, secretsTools, execTools, taskTools, textTools, guardTools, righthandSkills }
|
|
59
65
|
export { SKILL_NAME, SKILL_DESCRIPTION, skillDirectory, skillBody } from './skills.ts'
|
|
60
66
|
export type { GuardRule } from './guard-tools.ts'
|
|
61
67
|
export { guardFactsFor } from './guard-tools.ts'
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* task-tools — rh_task_* over ctx.storageDomain.
|
|
3
|
+
* A typed task board with a state machine: open → done | failed.
|
|
4
|
+
* `rh_task_next` is the queue discipline — the oldest open task is what to
|
|
5
|
+
* work on now. Descriptions carry the rule that a task's failure is recorded
|
|
6
|
+
* like an answer: a run that could not happen is news the owner needs.
|
|
7
|
+
* @module dsh-righthand/task-tools
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
11
|
+
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
12
|
+
import { defineDomain, domainTable } from '@deepseek-ai/dsh-storage-domain'
|
|
13
|
+
import type { Domain } from '@deepseek-ai/dsh-storage-domain'
|
|
14
|
+
import { z } from 'zod'
|
|
15
|
+
|
|
16
|
+
export const name = 'righthand-task'
|
|
17
|
+
export const inject = ['tools', 'storageDomain']
|
|
18
|
+
|
|
19
|
+
export type TaskState = 'open' | 'done' | 'failed'
|
|
20
|
+
|
|
21
|
+
export interface Task {
|
|
22
|
+
id: string
|
|
23
|
+
title: string
|
|
24
|
+
detail: string
|
|
25
|
+
due: string
|
|
26
|
+
state: TaskState
|
|
27
|
+
result: string
|
|
28
|
+
createdAt: string
|
|
29
|
+
updatedAt: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Domain spec: the typed task table. */
|
|
33
|
+
export const taskDomain = defineDomain({
|
|
34
|
+
name: 'righthand_tasks',
|
|
35
|
+
version: 1,
|
|
36
|
+
tables: {
|
|
37
|
+
tasks: domainTable(z.object({
|
|
38
|
+
id: z.string(),
|
|
39
|
+
title: z.string(),
|
|
40
|
+
detail: z.string(),
|
|
41
|
+
due: z.string(),
|
|
42
|
+
state: z.enum(['open', 'done', 'failed']),
|
|
43
|
+
result: z.string(),
|
|
44
|
+
createdAt: z.string(),
|
|
45
|
+
updatedAt: z.string(),
|
|
46
|
+
})),
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
export type TaskDomain = Domain<typeof taskDomain>
|
|
51
|
+
|
|
52
|
+
function newId(): string {
|
|
53
|
+
return 't-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 6)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Open the task domain and register the model-facing tools. */
|
|
57
|
+
export function apply(ctx: Context): void {
|
|
58
|
+
let domain: TaskDomain | undefined
|
|
59
|
+
let ready: Promise<void> | undefined
|
|
60
|
+
|
|
61
|
+
ctx.effect(() => {
|
|
62
|
+
ready = (async () => {
|
|
63
|
+
domain = await ctx.storageDomain.open(taskDomain)
|
|
64
|
+
})()
|
|
65
|
+
return () => { /* domain facility owns the domain */ }
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const ensure = async (): Promise<TaskDomain> => {
|
|
69
|
+
if (domain === undefined) { await ready; }
|
|
70
|
+
if (domain === undefined) throw new Error('righthand tasks are not open (storageDomain not mounted)')
|
|
71
|
+
return domain
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const all = async (): Promise<Task[]> => {
|
|
75
|
+
const d = await ensure()
|
|
76
|
+
return [...d.table('tasks').entries()].map(([, t]) => t)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
ctx.tools.register(defineTool({
|
|
80
|
+
name: 'rh_task_create',
|
|
81
|
+
description: 'Add a task with a title, optional detail and due date. Returns the created task with a generated id and state open. A task the agent cannot run is still recorded — its failure is delivered like an answer.',
|
|
82
|
+
parameters: {
|
|
83
|
+
title: { type: 'string', required: true, description: 'What the task is, e.g. "deploy vision-worker".' },
|
|
84
|
+
detail: { type: 'string', description: 'Optional longer description of what done means.' },
|
|
85
|
+
due: { type: 'string', description: 'Optional due date, YYYY-MM-DD.' },
|
|
86
|
+
},
|
|
87
|
+
output: {
|
|
88
|
+
schema: {
|
|
89
|
+
type: 'object',
|
|
90
|
+
additionalProperties: false,
|
|
91
|
+
properties: {
|
|
92
|
+
id: { type: 'string', required: true },
|
|
93
|
+
title: { type: 'string', required: true },
|
|
94
|
+
state: { type: 'string', required: true },
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
render: (_args, value) => [{ type: 'text', text: 'task ' + value.id + ' created: ' + value.title + ' (open)' }],
|
|
98
|
+
},
|
|
99
|
+
async execute(args) {
|
|
100
|
+
const d = await ensure()
|
|
101
|
+
const now = new Date().toISOString()
|
|
102
|
+
const task: Task = {
|
|
103
|
+
id: newId(),
|
|
104
|
+
title: args.title.trim(),
|
|
105
|
+
detail: args.detail ?? '',
|
|
106
|
+
due: args.due ?? '',
|
|
107
|
+
state: 'open',
|
|
108
|
+
result: '',
|
|
109
|
+
createdAt: now,
|
|
110
|
+
updatedAt: now,
|
|
111
|
+
}
|
|
112
|
+
if (task.title === '') throw new Error('a task needs a title')
|
|
113
|
+
await d.table('tasks').put(task.id, task)
|
|
114
|
+
return { id: task.id, title: task.title, state: task.state }
|
|
115
|
+
},
|
|
116
|
+
}))
|
|
117
|
+
ctx.tools.register(defineTool({
|
|
118
|
+
name: 'rh_task_list',
|
|
119
|
+
description: 'List tasks, open ones first (oldest open first). Optionally filter by state.',
|
|
120
|
+
parameters: {
|
|
121
|
+
state: { type: 'string', description: 'Filter: open | done | failed. Omit for all tasks.' },
|
|
122
|
+
},
|
|
123
|
+
output: {
|
|
124
|
+
schema: {
|
|
125
|
+
type: 'array',
|
|
126
|
+
items: {
|
|
127
|
+
type: 'object',
|
|
128
|
+
additionalProperties: false,
|
|
129
|
+
properties: {
|
|
130
|
+
id: { type: 'string', required: true },
|
|
131
|
+
title: { type: 'string', required: true },
|
|
132
|
+
state: { type: 'string', required: true },
|
|
133
|
+
due: { type: 'string' },
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
render: (_args, tasks) => [{ type: 'text', text: tasks.length === 0 ? '(no tasks)' : tasks.map((t: any) => '[' + t.state + '] ' + t.title).join(' / ') }],
|
|
138
|
+
},
|
|
139
|
+
async execute(args) {
|
|
140
|
+
const tasks = await all()
|
|
141
|
+
const wanted = args.state ?? null
|
|
142
|
+
const filtered = wanted ? tasks.filter(t => t.state === wanted) : tasks
|
|
143
|
+
const rank = { open: 0, done: 1, failed: 2 } as Record<string, number>
|
|
144
|
+
filtered.sort((a, b) => (rank[a.state] - rank[b.state]) || a.createdAt.localeCompare(b.createdAt))
|
|
145
|
+
return filtered.map(t => ({ id: t.id, title: t.title, state: t.state, ...t.due ? { due: t.due } : {} }))
|
|
146
|
+
},
|
|
147
|
+
}))
|
|
148
|
+
|
|
149
|
+
ctx.tools.register(defineTool({
|
|
150
|
+
name: 'rh_task_next',
|
|
151
|
+
description: 'The next open task — what to work on now. Oldest open task first. Returns found: false when nothing is open.',
|
|
152
|
+
parameters: {},
|
|
153
|
+
output: {
|
|
154
|
+
schema: {
|
|
155
|
+
type: 'object',
|
|
156
|
+
additionalProperties: false,
|
|
157
|
+
properties: {
|
|
158
|
+
found: { type: 'boolean', required: true },
|
|
159
|
+
id: { type: 'string' },
|
|
160
|
+
title: { type: 'string' },
|
|
161
|
+
detail: { type: 'string' },
|
|
162
|
+
due: { type: 'string' },
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
render: (_args, value) => [{ type: 'text', text: value.found ? 'next: ' + value.title : '(nothing open)' }],
|
|
166
|
+
},
|
|
167
|
+
async execute() {
|
|
168
|
+
const tasks = await all()
|
|
169
|
+
const open = tasks.filter(t => t.state === 'open').sort((a, b) => a.createdAt.localeCompare(b.createdAt))
|
|
170
|
+
const t = open[0]
|
|
171
|
+
if (t === undefined) return { found: false }
|
|
172
|
+
return { found: true, id: t.id, title: t.title, ...t.detail ? { detail: t.detail } : {}, ...t.due ? { due: t.due } : {} }
|
|
173
|
+
},
|
|
174
|
+
}))
|
|
175
|
+
ctx.tools.register(defineTool({
|
|
176
|
+
name: 'rh_task_update',
|
|
177
|
+
description: 'Change a task: its state (open | done | failed) and/or its result. A failed task should carry its result — what was tried and what went wrong — so the record says what happened, not just that something did.',
|
|
178
|
+
parameters: {
|
|
179
|
+
id: { type: 'string', required: true, description: 'The task id from rh_task_create or rh_task_list.' },
|
|
180
|
+
state: { type: 'string', description: 'New state: open | done | failed.' },
|
|
181
|
+
result: { type: 'string', description: 'What happened — the outcome, or the failure reason.' },
|
|
182
|
+
},
|
|
183
|
+
output: {
|
|
184
|
+
schema: {
|
|
185
|
+
type: 'object',
|
|
186
|
+
additionalProperties: false,
|
|
187
|
+
properties: {
|
|
188
|
+
found: { type: 'boolean', required: true },
|
|
189
|
+
id: { type: 'string', required: true },
|
|
190
|
+
state: { type: 'string', required: true },
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
render: (_args, value) => [{ type: 'text', text: value.found ? 'task ' + value.id + ' -> ' + value.state : 'task ' + value.id + ' not found' }],
|
|
194
|
+
},
|
|
195
|
+
async execute(args) {
|
|
196
|
+
const d = await ensure()
|
|
197
|
+
const task = d.table('tasks').get(args.id)
|
|
198
|
+
if (task === undefined) return { found: false, id: args.id, state: 'open' }
|
|
199
|
+
const next: Task = {
|
|
200
|
+
...task,
|
|
201
|
+
...args.state !== undefined ? { state: args.state as TaskState } : {},
|
|
202
|
+
...args.result !== undefined ? { result: args.result } : {},
|
|
203
|
+
updatedAt: new Date().toISOString(),
|
|
204
|
+
}
|
|
205
|
+
await d.table('tasks').put(args.id, next)
|
|
206
|
+
return { found: true, id: args.id, state: next.state }
|
|
207
|
+
},
|
|
208
|
+
}))
|
|
209
|
+
|
|
210
|
+
ctx.tools.register(defineTool({
|
|
211
|
+
name: 'rh_task_delete',
|
|
212
|
+
description: 'Remove a task by id. Returns whether it existed.',
|
|
213
|
+
parameters: {
|
|
214
|
+
id: { type: 'string', required: true, description: 'The task id to remove.' },
|
|
215
|
+
},
|
|
216
|
+
output: {
|
|
217
|
+
schema: {
|
|
218
|
+
type: 'object',
|
|
219
|
+
additionalProperties: false,
|
|
220
|
+
properties: {
|
|
221
|
+
id: { type: 'string', required: true },
|
|
222
|
+
existed: { type: 'boolean', required: true },
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
render: (_args, value) => [{ type: 'text', text: 'deleted ' + value.id + ': ' + value.existed }],
|
|
226
|
+
},
|
|
227
|
+
async execute(args) {
|
|
228
|
+
const d = await ensure()
|
|
229
|
+
const existed = await d.table('tasks').delete(args.id)
|
|
230
|
+
return { id: args.id, existed }
|
|
231
|
+
},
|
|
232
|
+
}))
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* text-tools — rh_text_* over ctx.llm.
|
|
3
|
+
* Language work at one call per verb: summarise, extract-to-JSON,
|
|
4
|
+
* classify, translate. Same prompt discipline as the digest summarizer:
|
|
5
|
+
* roles stay roles, caps are deliberate, each call's failure is contained
|
|
6
|
+
* in that call (the other tools are unaffected).
|
|
7
|
+
* @module dsh-righthand/text-tools
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
11
|
+
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
12
|
+
import { BlockAssembler, createUserMessage, deepFreeze } from '@deepseek-ai/dsh-llm'
|
|
13
|
+
import type { GenerateOptions } from '@deepseek-ai/dsh-llm'
|
|
14
|
+
|
|
15
|
+
export const name = 'righthand-text'
|
|
16
|
+
export const inject = ['tools', 'llm']
|
|
17
|
+
|
|
18
|
+
export interface TextConfig {
|
|
19
|
+
/** LLM route; defaults match the harness default model. */
|
|
20
|
+
provider?: string
|
|
21
|
+
model?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const DEFAULTS: Required<TextConfig> = { provider: 'deepseek-official', model: 'deepseek-v4-flash' }
|
|
25
|
+
|
|
26
|
+
/** One LLM call, text in, text out. Returns the assembled text or throws. */
|
|
27
|
+
async function ask(ctx: Context, cfg: Required<TextConfig>, system: string, user: string, maxTokens: number, signal: AbortSignal): Promise<string> {
|
|
28
|
+
const options: GenerateOptions = deepFreeze({
|
|
29
|
+
provider: cfg.provider,
|
|
30
|
+
model: cfg.model,
|
|
31
|
+
messages: [createUserMessage({
|
|
32
|
+
content: [{ type: 'text', text: user }],
|
|
33
|
+
source: { kind: 'plugin', plugin: 'righthand-text' },
|
|
34
|
+
})],
|
|
35
|
+
system,
|
|
36
|
+
maxTokens,
|
|
37
|
+
purpose: 'compaction',
|
|
38
|
+
signal,
|
|
39
|
+
})
|
|
40
|
+
const assembler = new BlockAssembler()
|
|
41
|
+
for await (const chunk of ctx.llm.stream(options)) assembler.push(chunk)
|
|
42
|
+
if (assembler.finish.kind !== 'stop') throw new Error('text model call did not stop cleanly: ' + JSON.stringify(assembler.finish.kind))
|
|
43
|
+
const text = assembler.blocks().filter((b: any) => b.type === 'text').map((b: any) => b.text).join(' ').trim()
|
|
44
|
+
if (text === '') throw new Error('text model call produced no text')
|
|
45
|
+
return text
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Extract the outermost {...} JSON object from a model reply. */
|
|
49
|
+
function parseJsonWindow(text: string): any {
|
|
50
|
+
const start = text.indexOf('{')
|
|
51
|
+
const end = text.lastIndexOf('}')
|
|
52
|
+
if (start < 0 || end <= start) throw new Error('model output was not JSON')
|
|
53
|
+
return JSON.parse(text.slice(start, end + 1))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Summarise: plain prose, no JSON. */
|
|
57
|
+
const SUMMARISE_SYSTEM = 'You summarise text. Return ONLY the summary itself - a few sentences, plain prose, no JSON, no Markdown headers, no quotes around it. Preserve the language of the input.'
|
|
58
|
+
|
|
59
|
+
/** Extract: JSON matching a caller schema. */
|
|
60
|
+
const EXTRACT_SYSTEM = 'You extract structured data from text. Return ONLY one JSON object matching the caller schema exactly - no prose, no Markdown, no code fences.'
|
|
61
|
+
|
|
62
|
+
/** Classify: one label plus confidence, as JSON. */
|
|
63
|
+
const CLASSIFY_SYSTEM = 'You classify text. Return ONLY one JSON object with exactly two keys: label (one of the given labels, verbatim) and confidence (a number from 0 to 1). No prose, no Markdown.'
|
|
64
|
+
|
|
65
|
+
/** Translate: preserve formatting, plain prose. */
|
|
66
|
+
const TRANSLATE_SYSTEM = 'You translate text. Return ONLY the translation - plain prose, no JSON, no quotes around it, preserving paragraph breaks and any Markdown or code the input contains.'
|
|
67
|
+
|
|
68
|
+
export function apply(ctx: Context, config: TextConfig = {}): void {
|
|
69
|
+
const cfg: Required<TextConfig> = { ...DEFAULTS, ...config }
|
|
70
|
+
ctx.tools.register(defineTool({
|
|
71
|
+
name: 'rh_text_summarise',
|
|
72
|
+
description: 'Summarise text into a few sentences. One model call per invocation; the result is new prose, not extracted sentences.',
|
|
73
|
+
parameters: {
|
|
74
|
+
text: { type: 'string', required: true, description: 'The text to summarise (max ~30000 characters).' },
|
|
75
|
+
sentences: { type: 'integer', description: 'Target length in sentences (default 3).' },
|
|
76
|
+
},
|
|
77
|
+
output: {
|
|
78
|
+
schema: {
|
|
79
|
+
type: 'object',
|
|
80
|
+
additionalProperties: false,
|
|
81
|
+
properties: {
|
|
82
|
+
summary: { type: 'string', required: true },
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
render: (_args, value) => [{ type: 'text', text: value.summary }],
|
|
86
|
+
},
|
|
87
|
+
async execute(args, exec) {
|
|
88
|
+
const n = args.sentences ?? 3
|
|
89
|
+
const summary = await ask(ctx, cfg, SUMMARISE_SYSTEM, 'Summarise this in about ' + n + ' sentences:\n\n' + args.text, 600, exec.signal)
|
|
90
|
+
return { summary }
|
|
91
|
+
},
|
|
92
|
+
}))
|
|
93
|
+
ctx.tools.register(defineTool({
|
|
94
|
+
name: 'rh_text_extract',
|
|
95
|
+
description: 'Turn text into JSON matching a schema you give. The schema is a JSON Schema object; the model returns one object conforming to it, or the call fails cleanly.',
|
|
96
|
+
parameters: {
|
|
97
|
+
text: { type: 'string', required: true, description: 'The text to extract from.' },
|
|
98
|
+
schema: { type: 'json', required: true, description: 'JSON Schema the result must match, e.g. { "type": "object", "properties": { "name": { "type": "string" } }, "required": ["name"] }.' },
|
|
99
|
+
},
|
|
100
|
+
output: {
|
|
101
|
+
schema: {
|
|
102
|
+
type: 'object',
|
|
103
|
+
additionalProperties: false,
|
|
104
|
+
properties: {
|
|
105
|
+
extracted: { type: 'json', required: true },
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
render: (_args, value) => [{ type: 'text', text: JSON.stringify(value.extracted) }],
|
|
109
|
+
},
|
|
110
|
+
async execute(args, exec) {
|
|
111
|
+
const framed = 'Schema:\n' + JSON.stringify(args.schema) + '\n\nText:\n' + args.text
|
|
112
|
+
const raw = await ask(ctx, cfg, EXTRACT_SYSTEM, framed, 800, exec.signal)
|
|
113
|
+
return { extracted: parseJsonWindow(raw) }
|
|
114
|
+
},
|
|
115
|
+
}))
|
|
116
|
+
ctx.tools.register(defineTool({
|
|
117
|
+
name: 'rh_text_classify',
|
|
118
|
+
description: 'Sort text into one of the labels you give, with a confidence score from 0 to 1. The label returned is one of yours, verbatim.',
|
|
119
|
+
parameters: {
|
|
120
|
+
text: { type: 'string', required: true, description: 'The text to classify.' },
|
|
121
|
+
labels: { type: 'array', required: true, items: { type: 'string' }, description: 'The candidate labels, e.g. ["bug", "feature request", "question"].' },
|
|
122
|
+
},
|
|
123
|
+
output: {
|
|
124
|
+
schema: {
|
|
125
|
+
type: 'object',
|
|
126
|
+
additionalProperties: false,
|
|
127
|
+
properties: {
|
|
128
|
+
label: { type: 'string', required: true },
|
|
129
|
+
confidence: { type: 'number', required: true },
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
render: (_args, value) => [{ type: 'text', text: value.label + ' (' + value.confidence + ')' }],
|
|
133
|
+
},
|
|
134
|
+
async execute(args, exec) {
|
|
135
|
+
if (args.labels.length === 0) throw new Error('classify needs at least one label')
|
|
136
|
+
const framed = 'Labels: ' + JSON.stringify(args.labels) + '\n\nText:\n' + args.text
|
|
137
|
+
const raw = await ask(ctx, cfg, CLASSIFY_SYSTEM, framed, 100, exec.signal)
|
|
138
|
+
const parsed = parseJsonWindow(raw)
|
|
139
|
+
if (typeof parsed.label !== 'string' || !args.labels.includes(parsed.label)) throw new Error('model returned a label outside the given set')
|
|
140
|
+
const confidence = Number(parsed.confidence)
|
|
141
|
+
if (!Number.isFinite(confidence) || confidence < 0 || confidence > 1) throw new Error('model returned an invalid confidence')
|
|
142
|
+
return { label: parsed.label, confidence }
|
|
143
|
+
},
|
|
144
|
+
}))
|
|
145
|
+
ctx.tools.register(defineTool({
|
|
146
|
+
name: 'rh_text_translate',
|
|
147
|
+
description: 'Translate text into another language, preserving formatting (paragraph breaks, Markdown, code).',
|
|
148
|
+
parameters: {
|
|
149
|
+
text: { type: 'string', required: true, description: 'The text to translate.' },
|
|
150
|
+
language: { type: 'string', required: true, description: 'The target language, e.g. "Spanish", "zh-CN", "Japanese".' },
|
|
151
|
+
},
|
|
152
|
+
output: {
|
|
153
|
+
schema: {
|
|
154
|
+
type: 'object',
|
|
155
|
+
additionalProperties: false,
|
|
156
|
+
properties: {
|
|
157
|
+
translation: { type: 'string', required: true },
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
render: (_args, value) => [{ type: 'text', text: value.translation }],
|
|
161
|
+
},
|
|
162
|
+
async execute(args, exec) {
|
|
163
|
+
const framed = 'Translate the following into ' + args.language + ':\n\n' + args.text
|
|
164
|
+
const translation = await ask(ctx, cfg, TRANSLATE_SYSTEM, framed, 800, exec.signal)
|
|
165
|
+
return { translation }
|
|
166
|
+
},
|
|
167
|
+
}))
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
|