@hydraharness/harness-command-feedback 0.0.0-stage → 0.1.1-rc.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeepSeek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,71 @@
1
- # Temporary Holding Version
1
+ # @hydraharness/harness-command-feedback
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Trigger-independent session feedback plus human-facing `/feedback` capture. The package exports `recordFeedback(session, text)`, which appends one log-only `feedback/record` event. Its plugin registers one global command through [`ctx.commands`](../../interaction/commands/README.md), so every composed command adapter discovers it; the shipped Web client executes it without a model turn.
4
+
5
+ ## Command contract
6
+
7
+ | Input | Result |
8
+ |---|---|
9
+ | `/feedback <text>` | Append `feedback/record` and acknowledge with `Feedback recorded for session {sessionId}`, `Anonymous user: {userId}`, plus the session-sharing disclosure. |
10
+ | `/feedback` | Return a direct usage error. Whitespace-only input is treated as empty. |
11
+
12
+ Surrounding whitespace is discarded, but feedback is otherwise unparsed: no truncation, case folding, or control words. Text that looks like another command, such as `/feedback /plan felt slow`, is feedback content. Repeated commands each produce their own event; nothing is replaced or merged.
13
+
14
+ ## Session-sharing disclosure
15
+
16
+ The acknowledgement names the receiving session id and reports how that session is shared, read from the mounted [`telemetry`](../../session/session-telemetry/README.md) service through the plugin context (`ctx.get('telemetry')`, never a declared injection). The disclosure is one sentence chosen from the backend's [`SessionTelemetrySharingStatus`](../../session/session-telemetry/README.md):
17
+
18
+ | Disclosed status | Acknowledgement sentence |
19
+ |---|---|
20
+ | `full` | `Session sharing is enabled.` |
21
+ | `feedback-only` | `Session sharing is feedback-gated; recording feedback releases the session prefix for sharing.` |
22
+ | `disabled` | `Session sharing is disabled.` |
23
+ | no service | `Session sharing is not configured.` |
24
+
25
+ The disclosure states the deployment's current sharing policy only; it never promises delivery or retention. With `full` or `feedback-only`, records are handed to the backend's non-blocking enqueue and the SDK owns batching, retry, and loss policy, so the sentence claims nothing about what reached a collector; `disabled` claims nothing about future reconfiguration. The disclosure adds no event and never enters the model surface.
26
+
27
+ ## What this plugin does and does not do
28
+
29
+ `recordFeedback(session, text)` is the command-independent write path. It rejects empty normalized text and appends `feedback/record { text }`; a different UI, hook, or host integration can call it without constructing a slash command. The `/feedback` handler uses that producer and starts no model work. The optional [`@hydraharness/harness-session-telemetry-otel`](../../session/session-telemetry-otel) consumer observes the event without changing its capture contract.
30
+
31
+ The feedback text appears in exactly one durable payload: `feedback/record`. [`@hydraharness/harness-commands`](../../interaction/commands/README.md) still appends its generic `command/run` / `command/done` pairing, but this definition sets `recordInput: false`, so `command/run` omits `args`; the paired `command/done` carries only the outcome. All three events are log-only and absent from the ordered surface, `deriveMessages()`, and model requests. These appends start persistence's ordinary eager drain, but neither producer forces `session/flush`, so acknowledgement means the feedback is in the log, not that it has reached disk. The acknowledgement identifies both the receiving session and the [shared anonymous user](../../identity/anonymous-user-id/); the first accepted feedback for a harness home can create `$HYDRA_HOME/.anonymous-user-id`. Rejected empty input leaves only the command pairing settled as `kind: 'error'`, with no `feedback/record` and no user-id lookup.
32
+
33
+ The event is authoritative rather than the command record because feedback may arrive through a trigger other than `/feedback`. Keeping the payload out of `command/run` avoids two records carrying the same text.
34
+
35
+ ## Composition
36
+
37
+ The producer injects only `commands`. A custom app mounts the registry plus this plugin:
38
+
39
+ ```yaml
40
+ - id: commands
41
+ name: '@hydraharness/harness-commands'
42
+ - id: command-feedback
43
+ name: '@hydraharness/harness-command-feedback'
44
+ ```
45
+
46
+ The shipped `hydra` base mounts this command unconditionally; it has no configuration and no dependency on the persisted-goal stack. The Web client exposes it through the command adapter. Headless mode, ACP automation, and JSON-RPC do not provide a command adapter, so they do not expose it.
47
+
48
+ ## Model Experience
49
+
50
+ ### Human `/feedback` capture
51
+
52
+ #### What the model sees
53
+
54
+ Nothing. The slash input, `feedback/record`, and the acknowledgement are absent from model requests. The feedback event and registry lifecycle records are log-only and carry no `surfaceOp`, so they never reach the ordered surface, `deriveMessages()`, or a system prompt. Recording feedback during a turn does not change that turn's remaining requests.
55
+
56
+ #### Token effect
57
+
58
+ Zero direct token effect. Neither an accepted entry nor a usage error adds model tokens, in the recording turn or any later one.
59
+
60
+ #### KV Cache effect
61
+
62
+ Independent of the model request path. Recording appends to the session log only, leaving an already-reusable request prefix untouched. Nothing this package contributes can invalidate cache reuse.
63
+
64
+ ## Known Limitations and Deferred Work
65
+
66
+ - **No feedback retrieval or management surface** — the optional OTel plugin uses the event only as a sharing trigger. There is no retrieval, aggregation, categorization, or model-facing tool for `feedback/record`.
67
+ - **No structured fields** — an entry is one free-text string with no category, severity, or referenced-event link, so feedback cannot be filtered by subject without re-reading its text.
68
+ - **No amend or withdraw** — the session log is append-only and this package adds no tombstone, so a mistaken entry stays recorded and can only be superseded by a later one.
69
+ - **No explicit durability barrier** — the acknowledgement follows the append, not a flush, so an entry recorded immediately before a crash can be lost with any other unflushed tail. Feedback is not worth forcing a synchronous disk write for; a consumer that needs one awaits `ctx.sessions.flush(session)`.
70
+ - **No visible acknowledgement on a fresh session** — the web transcript renders command rows only once a session is active, so `/feedback` on a still-blank session records the event but shows no acknowledgement row. Recording feedback after the first message renders normally.
71
+ - **Web only among the shipped entry points** — headless mode, ACP automation, and JSON-RPC do not provide a command adapter, so `/feedback` is unavailable there.
package/lib/index.js ADDED
@@ -0,0 +1,83 @@
1
+ import { getOrCreateAnonymousUserId } from "@hydraharness/harness-anonymous-user-id";
2
+ //#region lib/types/index.js
3
+ /**
4
+ * Session feedback event plus the human-facing `/feedback` producer. Recording
5
+ * appends one authoritative log-only event and does not start model work. The
6
+ * append is eager but unflushed, so acknowledgement reports that the entry is
7
+ * logged, not that it reached disk.
8
+ * @module @hydraharness/harness-command-feedback
9
+ */
10
+ const name = "command-feedback";
11
+ const inject = ["commands"];
12
+ const USAGE = "Usage: /feedback <text>";
13
+ /** Fail closed when a future sharing status reaches the sentence switch. */
14
+ /* v8 ignore next 3 -- only the ignored default arm calls this; the closed union cannot reach it via the public API. */
15
+ function assertNever(value) {
16
+ throw new Error(`command-feedback: unsupported sharing status ${JSON.stringify(value)}`);
17
+ }
18
+ /** The acknowledgement's sharing sentence for a disclosed policy. */
19
+ function sharingSentence(sharing) {
20
+ switch (sharing) {
21
+ case "full": return "Session sharing is enabled.";
22
+ case "feedback-only": return "Session sharing is feedback-gated; recording feedback releases the session prefix for sharing.";
23
+ case "disabled": return "Session sharing is disabled.";
24
+ /* v8 ignore next 2 -- the seam's closed union cannot reach the default; a future status must be given a sentence here. */
25
+ default: return assertNever(sharing);
26
+ }
27
+ }
28
+ /**
29
+ * The sharing disclosure appended to the acknowledgement: the mounted
30
+ * backend's disclosed policy, or a "not configured" notice when no backend
31
+ * is mounted. Read through the plugin context so the command still works
32
+ * when the telemetry service is absent.
33
+ * @param telemetry - the mounted telemetry service, or undefined.
34
+ * @returns one sentence describing this session's sharing policy.
35
+ */
36
+ function sharingDisclosure(telemetry) {
37
+ if (telemetry === void 0) return "Session sharing is not configured.";
38
+ return sharingSentence(telemetry.sharing);
39
+ }
40
+ /**
41
+ * Record feedback independently of any UI trigger.
42
+ * @param session - session the feedback describes.
43
+ * @param text - human-authored feedback; surrounding whitespace is discarded.
44
+ * @throws {TypeError} when the normalized text is empty.
45
+ */
46
+ function recordFeedback(session, text) {
47
+ const normalized = text.trim();
48
+ if (normalized.length === 0) throw new TypeError("feedback text must not be empty");
49
+ session.append("feedback/record", { text: normalized });
50
+ }
51
+ /**
52
+ * Validate, record, and acknowledge one feedback entry. Returning an error
53
+ * leaves no `feedback/record` event.
54
+ * @param invocation - receiving agent, raw command input, and UI cancellation.
55
+ * @param ctx - plugin context used to read the optional telemetry service.
56
+ * @returns an acknowledgement containing the receiving session and anonymous
57
+ * user ids plus the session-sharing disclosure, or a usage error when no
58
+ * feedback text was supplied.
59
+ */
60
+ function executeFeedbackCommand(invocation, ctx) {
61
+ if (invocation.rawInput.trim().length === 0) return {
62
+ kind: "error",
63
+ text: `Feedback text is required. ${USAGE}`
64
+ };
65
+ recordFeedback(invocation.agent.session, invocation.rawInput);
66
+ const telemetry = ctx.get("sessionTelemetry");
67
+ return {
68
+ kind: "success",
69
+ text: `Feedback recorded for session ${invocation.agent.session.id}\nAnonymous user: ${getOrCreateAnonymousUserId()}. ${sharingDisclosure(telemetry)}`
70
+ };
71
+ }
72
+ /** Register the global `/feedback` command for every composed command adapter. */
73
+ function apply(ctx) {
74
+ ctx.commands.register({
75
+ name: "feedback",
76
+ description: "record feedback about this session",
77
+ input: { hint: "<text>" },
78
+ recordInput: false,
79
+ handler: (invocation) => executeFeedbackCommand(invocation, ctx)
80
+ });
81
+ }
82
+ //#endregion
83
+ export { apply, inject, name, recordFeedback };
@@ -0,0 +1,23 @@
1
+ //#region lib/types/invariant.js
2
+ /**
3
+ * Package-owned invariant companion for `@hydraharness/harness-command-feedback`.
4
+ * @module @hydraharness/harness-command-feedback/invariant
5
+ */
6
+ const PACKAGE_NAME = "@hydraharness/harness-command-feedback";
7
+ /** Cordis companion plugin name. */
8
+ const name = "command-feedback-invariant";
9
+ /** Service required before the companion can reserve package ownership. */
10
+ const inject = ["invariants"];
11
+ /**
12
+ * No runtime invariant: each `feedback/record` is an independent append-only
13
+ * fact with no cross-event or mutable-data relationship.
14
+ */
15
+ const install = () => {};
16
+ /**
17
+ * Register this package's invariant companion.
18
+ * @param ctx - Cordis context carrying the invariant service.
19
+ * @returns the installed registration's disposer after setup succeeds.
20
+ */
21
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
22
+ //#endregion
23
+ export { apply, inject, name };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Session feedback event plus the human-facing `/feedback` producer. Recording
3
+ * appends one authoritative log-only event and does not start model work. The
4
+ * append is eager but unflushed, so acknowledgement reports that the entry is
5
+ * logged, not that it reached disk.
6
+ * @module @hydraharness/harness-command-feedback
7
+ */
8
+ import type { Context } from '@hydraharness/cordis';
9
+ import type { Session } from '@hydraharness/harness-session';
10
+ export declare const name = "command-feedback";
11
+ export declare const inject: string[];
12
+ declare module '@hydraharness/harness-session/types' {
13
+ interface SessionEventMap {
14
+ /**
15
+ * One recorded human remark about this session. Log-only and independent
16
+ * of its trigger; it never enters model context or derived history.
17
+ */
18
+ 'feedback/record': {
19
+ text: string;
20
+ };
21
+ }
22
+ }
23
+ /**
24
+ * Record feedback independently of any UI trigger.
25
+ * @param session - session the feedback describes.
26
+ * @param text - human-authored feedback; surrounding whitespace is discarded.
27
+ * @throws {TypeError} when the normalized text is empty.
28
+ */
29
+ export declare function recordFeedback(session: Session, text: string): void;
30
+ /** Register the global `/feedback` command for every composed command adapter. */
31
+ export declare function apply(ctx: Context): void;
32
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Package-owned invariant companion for `@hydraharness/harness-command-feedback`.
3
+ * @module @hydraharness/harness-command-feedback/invariant
4
+ */
5
+ import type { Context } from '@hydraharness/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "command-feedback-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ export declare const apply: (ctx: Context) => Promise<() => void>;
16
+ //# sourceMappingURL=invariant.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,59 @@
1
1
  {
2
2
  "name": "@hydraharness/harness-command-feedback",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "description": "Log-only session feedback producer and human-facing slash command",
4
+ "hydra": {
5
+ "plugin": {
6
+ "application": "Record session feedback through a slash command."
7
+ }
8
+ },
9
+ "version": "0.1.1-rc.5",
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/MaiHongPhong1902/Hydra-Harness.git",
16
+ "directory": "packages/feedback/command-feedback"
17
+ },
18
+ "type": "module",
19
+ "main": "lib/index.js",
20
+ "types": "lib/types/index.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./lib/types/index.d.ts",
24
+ "default": "./lib/index.js"
25
+ },
26
+ "./invariant": {
27
+ "types": "./lib/types/invariant.d.ts",
28
+ "default": "./lib/invariant.js"
29
+ },
30
+ "./src/*": "./src/*",
31
+ "./package.json": "./package.json"
32
+ },
33
+ "files": [
34
+ "lib/index.js",
35
+ "lib/invariant.js",
36
+ "lib/types/**/*.d.ts"
37
+ ],
38
+ "license": "MIT",
39
+ "peerDependencies": {
40
+ "@hydraharness/harness-commands": "^0.1.1-rc.5",
41
+ "@hydraharness/harness-invariants": "^0.1.1-rc.5",
42
+ "@hydraharness/harness-session": "^0.1.1-rc.5",
43
+ "@hydraharness/harness-session-telemetry": "^0.1.1-rc.5",
44
+ "@hydraharness/harness-anonymous-user-id": "^0.1.1-rc.5",
45
+ "@hydraharness/cordis": "^4.0.2"
46
+ },
47
+ "devDependencies": {
48
+ "@hydraharness/cordis-plugin-loader": "^1.0.3",
49
+ "@hydraharness/harness-commands": "^0.1.1-rc.5",
50
+ "@hydraharness/harness-agent": "^0.1.1-rc.5",
51
+ "@hydraharness/harness-invariants": "^0.1.1-rc.5",
52
+ "@hydraharness/cordis-plugin-include": "^1.0.7",
53
+ "@hydraharness/harness-llm": "^0.1.1-rc.5",
54
+ "@hydraharness/harness-session-telemetry": "^0.1.1-rc.5",
55
+ "@hydraharness/harness-session": "^0.1.1-rc.5",
56
+ "@hydraharness/harness-anonymous-user-id": "^0.1.1-rc.5",
57
+ "@hydraharness/cordis": "^4.0.2"
58
+ }
6
59
  }