@husk-ai/core 0.1.1

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.
Files changed (61) hide show
  1. package/dist/audit.d.ts +65 -0
  2. package/dist/audit.d.ts.map +1 -0
  3. package/dist/audit.js +87 -0
  4. package/dist/audit.js.map +1 -0
  5. package/dist/browse.d.ts +66 -0
  6. package/dist/browse.d.ts.map +1 -0
  7. package/dist/browse.js +217 -0
  8. package/dist/browse.js.map +1 -0
  9. package/dist/config.d.ts +73 -0
  10. package/dist/config.d.ts.map +1 -0
  11. package/dist/config.js +61 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/errors.d.ts +59 -0
  14. package/dist/errors.d.ts.map +1 -0
  15. package/dist/errors.js +40 -0
  16. package/dist/errors.js.map +1 -0
  17. package/dist/ids.d.ts +7 -0
  18. package/dist/ids.d.ts.map +1 -0
  19. package/dist/ids.js +29 -0
  20. package/dist/ids.js.map +1 -0
  21. package/dist/index.d.ts +23 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +19 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/lifecycle.d.ts +38 -0
  26. package/dist/lifecycle.d.ts.map +1 -0
  27. package/dist/lifecycle.js +52 -0
  28. package/dist/lifecycle.js.map +1 -0
  29. package/dist/logger.d.ts +20 -0
  30. package/dist/logger.d.ts.map +1 -0
  31. package/dist/logger.js +59 -0
  32. package/dist/logger.js.map +1 -0
  33. package/dist/net.d.ts +48 -0
  34. package/dist/net.d.ts.map +1 -0
  35. package/dist/net.js +219 -0
  36. package/dist/net.js.map +1 -0
  37. package/dist/spec.d.ts +832 -0
  38. package/dist/spec.d.ts.map +1 -0
  39. package/dist/spec.js +191 -0
  40. package/dist/spec.js.map +1 -0
  41. package/dist/types/agent.d.ts +168 -0
  42. package/dist/types/agent.d.ts.map +1 -0
  43. package/dist/types/agent.js +2 -0
  44. package/dist/types/agent.js.map +1 -0
  45. package/dist/types/computer.d.ts +205 -0
  46. package/dist/types/computer.d.ts.map +1 -0
  47. package/dist/types/computer.js +9 -0
  48. package/dist/types/computer.js.map +1 -0
  49. package/dist/types/model.d.ts +197 -0
  50. package/dist/types/model.d.ts.map +1 -0
  51. package/dist/types/model.js +16 -0
  52. package/dist/types/model.js.map +1 -0
  53. package/dist/types/transcript.d.ts +64 -0
  54. package/dist/types/transcript.d.ts.map +1 -0
  55. package/dist/types/transcript.js +3 -0
  56. package/dist/types/transcript.js.map +1 -0
  57. package/dist/util.d.ts +69 -0
  58. package/dist/util.d.ts.map +1 -0
  59. package/dist/util.js +236 -0
  60. package/dist/util.js.map +1 -0
  61. package/package.json +26 -0
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Two of these are reserved rather than live. See `RESERVED_ERROR_CODES`.
3
+ */
4
+ export type HuskErrorCode = 'E_PROVIDER_UNAVAILABLE' | 'E_COMPUTER_NOT_FOUND' | 'E_COMPUTER_FAILED' | 'E_EXEC_FAILED'
5
+ /**
6
+ * Reserved. Nothing throws this.
7
+ *
8
+ * An exec that runs out of time still produces stdout, stderr and an exit
9
+ * code, and the caller usually wants all three -- so a timeout is reported as
10
+ * `ExecResult.timedOut: true`, not as a thrown error. Throwing would discard
11
+ * the output the agent needs in order to explain what happened.
12
+ *
13
+ * Kept because it is the honest code for the *status* of such a result: the
14
+ * server maps it to 504 and `@husk-ai/sdk` decodes 504 back to it, so a proxy or
15
+ * gateway timing out a request still round-trips into a sensible `HuskError`.
16
+ */
17
+ | 'E_EXEC_TIMEOUT' | 'E_EXEC_DENIED' | 'E_FS_DENIED' | 'E_QUOTA' | 'E_MODEL_UNAVAILABLE' | 'E_MODEL_ERROR' | 'E_NO_CREDENTIALS' | 'E_SPEC_INVALID' | 'E_IMPORT_FAILED' | 'E_TOOL_ERROR' | 'E_BUDGET_EXCEEDED'
18
+ /**
19
+ * Reserved. Nothing throws this.
20
+ *
21
+ * Hitting `limits.maxSteps` is a normal ending, not a failure: the run has
22
+ * real text, real messages and real usage to return, and the caller learns
23
+ * why it stopped from `RunResult.stopReason: 'step_limit'`. Throwing here
24
+ * would throw the run away with it.
25
+ */
26
+ | 'E_STEP_LIMIT' | 'E_ABORTED' | 'E_NOT_IMPLEMENTED' | 'E_CONFIG' | 'E_INTERNAL';
27
+ /**
28
+ * Codes that are part of the contract but are never thrown by husk itself.
29
+ *
30
+ * They are declared so the status mapping in `@husk-ai/server` and the decoder in
31
+ * `@husk-ai/sdk` have something to name, and so removing one is a deliberate
32
+ * breaking change rather than an accident. The condition each describes
33
+ * surfaces as data -- `ExecResult.timedOut`, `RunResult.stopReason` -- because
34
+ * a timeout or a step ceiling is a result the caller wants to read, not an
35
+ * exception that destroys it.
36
+ */
37
+ export declare const RESERVED_ERROR_CODES: readonly ["E_EXEC_TIMEOUT", "E_STEP_LIMIT"];
38
+ export declare class HuskError extends Error {
39
+ readonly code: HuskErrorCode;
40
+ readonly details?: Record<string, unknown>;
41
+ /** A one-line, human-actionable next step. Surfaced verbatim by the CLI. */
42
+ readonly hint?: string;
43
+ constructor(code: HuskErrorCode, message: string, opts?: {
44
+ hint?: string;
45
+ details?: Record<string, unknown>;
46
+ cause?: unknown;
47
+ });
48
+ toJSON(): {
49
+ name: string;
50
+ code: HuskErrorCode;
51
+ message: string;
52
+ hint: string | undefined;
53
+ details: Record<string, unknown> | undefined;
54
+ };
55
+ }
56
+ export declare function isHuskError(e: unknown): e is HuskError;
57
+ /** Never throw from a cleanup path. */
58
+ export declare function quiet<T>(fn: () => Promise<T>): Promise<T | undefined>;
59
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,aAAa,GACrB,wBAAwB,GACxB,sBAAsB,GACtB,mBAAmB,GACnB,eAAe;AACjB;;;;;;;;;;;GAWG;GACD,gBAAgB,GAChB,eAAe,GACf,aAAa,GACb,SAAS,GACT,qBAAqB,GACrB,eAAe,GACf,kBAAkB,GAClB,gBAAgB,GAChB,iBAAiB,GACjB,cAAc,GACd,mBAAmB;AACrB;;;;;;;GAOG;GACD,cAAc,GACd,WAAW,GACX,mBAAmB,GACnB,UAAU,GACV,YAAY,CAAC;AAEjB;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,6CAAiF,CAAC;AAEnH,qBAAa,SAAU,SAAQ,KAAK;IAClC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC3C,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;gBAGrB,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO;IASlF,MAAM;;;;;;;CAGP;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,SAAS,CAEtD;AAED,uCAAuC;AACvC,wBAAsB,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAM3E"}
package/dist/errors.js ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Codes that are part of the contract but are never thrown by husk itself.
3
+ *
4
+ * They are declared so the status mapping in `@husk-ai/server` and the decoder in
5
+ * `@husk-ai/sdk` have something to name, and so removing one is a deliberate
6
+ * breaking change rather than an accident. The condition each describes
7
+ * surfaces as data -- `ExecResult.timedOut`, `RunResult.stopReason` -- because
8
+ * a timeout or a step ceiling is a result the caller wants to read, not an
9
+ * exception that destroys it.
10
+ */
11
+ export const RESERVED_ERROR_CODES = ['E_EXEC_TIMEOUT', 'E_STEP_LIMIT'];
12
+ export class HuskError extends Error {
13
+ code;
14
+ details;
15
+ /** A one-line, human-actionable next step. Surfaced verbatim by the CLI. */
16
+ hint;
17
+ constructor(code, message, opts = {}) {
18
+ super(message, opts.cause !== undefined ? { cause: opts.cause } : undefined);
19
+ this.name = 'HuskError';
20
+ this.code = code;
21
+ this.hint = opts.hint;
22
+ this.details = opts.details;
23
+ }
24
+ toJSON() {
25
+ return { name: this.name, code: this.code, message: this.message, hint: this.hint, details: this.details };
26
+ }
27
+ }
28
+ export function isHuskError(e) {
29
+ return e instanceof HuskError;
30
+ }
31
+ /** Never throw from a cleanup path. */
32
+ export async function quiet(fn) {
33
+ try {
34
+ return await fn();
35
+ }
36
+ catch {
37
+ return undefined;
38
+ }
39
+ }
40
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AA6CA;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,gBAAgB,EAAE,cAAc,CAA6C,CAAC;AAEnH,MAAM,OAAO,SAAU,SAAQ,KAAK;IACzB,IAAI,CAAgB;IACpB,OAAO,CAA2B;IAC3C,4EAA4E;IACnE,IAAI,CAAU;IAEvB,YACE,IAAmB,EACnB,OAAe,EACf,OAA8E,EAAE;QAEhF,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAC7E,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;IAC9B,CAAC;IAED,MAAM;QACJ,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;IAC7G,CAAC;CACF;AAED,MAAM,UAAU,WAAW,CAAC,CAAU;IACpC,OAAO,CAAC,YAAY,SAAS,CAAC;AAChC,CAAC;AAED,uCAAuC;AACvC,MAAM,CAAC,KAAK,UAAU,KAAK,CAAI,EAAoB;IACjD,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,EAAE,CAAC;IACpB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC"}
package/dist/ids.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ /** Short, URL-safe id. */
2
+ export declare function id(prefix?: string, len?: number): string;
3
+ /** Id with a millisecond time prefix -- sorts lexicographically by creation. */
4
+ export declare function tid(prefix?: string): string;
5
+ /** Deterministic, filesystem-safe slug. */
6
+ export declare function slug(input: string, max?: number): string;
7
+ //# sourceMappingURL=ids.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ids.d.ts","sourceRoot":"","sources":["../src/ids.ts"],"names":[],"mappings":"AAIA,0BAA0B;AAC1B,wBAAgB,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,SAAK,GAAG,MAAM,CAKpD;AAED,gFAAgF;AAChF,wBAAgB,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAG3C;AAED,2CAA2C;AAC3C,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,SAAK,GAAG,MAAM,CAWpD"}
package/dist/ids.js ADDED
@@ -0,0 +1,29 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ const ALPHABET = '0123456789abcdefghjkmnpqrstvwxyz'; // Crockford-ish, no i/l/o/u
3
+ /** Short, URL-safe id. */
4
+ export function id(prefix, len = 12) {
5
+ const bytes = randomBytes(len);
6
+ let out = '';
7
+ for (let i = 0; i < len; i++)
8
+ out += ALPHABET[bytes[i] % ALPHABET.length];
9
+ return prefix ? `${prefix}_${out}` : out;
10
+ }
11
+ /** Id with a millisecond time prefix -- sorts lexicographically by creation. */
12
+ export function tid(prefix) {
13
+ const t = Date.now().toString(36).padStart(9, '0');
14
+ return `${prefix ? prefix + '_' : ''}${t}${id(undefined, 6)}`;
15
+ }
16
+ /** Deterministic, filesystem-safe slug. */
17
+ export function slug(input, max = 48) {
18
+ const s = input
19
+ .normalize('NFKD')
20
+ .replace(/[^\w\s-]/g, '')
21
+ .trim()
22
+ .replace(/[\s_]+/g, '-')
23
+ .replace(/-+/g, '-')
24
+ .toLowerCase()
25
+ .slice(0, max)
26
+ .replace(/^-|-$/g, '');
27
+ return s || 'husk';
28
+ }
29
+ //# sourceMappingURL=ids.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ids.js","sourceRoot":"","sources":["../src/ids.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,QAAQ,GAAG,kCAAkC,CAAC,CAAC,4BAA4B;AAEjF,0BAA0B;AAC1B,MAAM,UAAU,EAAE,CAAC,MAAe,EAAE,GAAG,GAAG,EAAE;IAC1C,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE;QAAE,GAAG,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC3E,OAAO,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;AAC3C,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,GAAG,CAAC,MAAe;IACjC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACnD,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC;AAChE,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,IAAI,CAAC,KAAa,EAAE,GAAG,GAAG,EAAE;IAC1C,MAAM,CAAC,GAAG,KAAK;SACZ,SAAS,CAAC,MAAM,CAAC;SACjB,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC;SACxB,IAAI,EAAE;SACN,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC;SACvB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,WAAW,EAAE;SACb,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;SACb,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACzB,OAAO,CAAC,IAAI,MAAM,CAAC;AACrB,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @husk-ai/core -- the contracts.
3
+ *
4
+ * Every other package depends on this one and nothing else in the workspace.
5
+ * If a type belongs to more than one package, it belongs here.
6
+ */
7
+ export * from './ids.js';
8
+ export * from './errors.js';
9
+ export * from './logger.js';
10
+ export * from './util.js';
11
+ export * from './lifecycle.js';
12
+ export * from './audit.js';
13
+ export * from './config.js';
14
+ export * from './spec.js';
15
+ export * from './net.js';
16
+ export * from './browse.js';
17
+ export type { ProviderName, ComputerState, NetworkPolicy, MountSpec, Flavor, ComputerSpec, ComputerInfo, PortBinding, ExecRequest, ExecResult, DirEntry, WriteFileOptions, Computer, Availability, ComputerProvider, } from './types/computer.js';
18
+ export type { Role, TextPart, ImagePart, ToolCallPart, ToolResultPart, ThinkingPart, ContentPart, ModelMessage, JSONSchema, ToolSchema, ToolChoice, ChatRequest, Usage, FinishReason, ChatResponse, StreamEvent, ModelInfo, ModelProvider, } from './types/model.js';
19
+ export { messageText } from './types/model.js';
20
+ export type { ToolContext, Tool, ApprovalMode, ApprovalRequest, Approver, RunOptions, RunEvent, RunResult, } from './types/agent.js';
21
+ export type { TranscriptSource, TranscriptMessage, Transcript, ImportInput, TranscriptImporter, DistilledAgent, } from './types/transcript.js';
22
+ export declare const HUSK_VERSION = "0.1.0";
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAG5B,YAAY,EACV,YAAY,EACZ,aAAa,EACb,aAAa,EACb,SAAS,EACT,MAAM,EACN,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,WAAW,EACX,UAAU,EACV,QAAQ,EACR,gBAAgB,EAChB,QAAQ,EACR,YAAY,EACZ,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EACV,IAAI,EACJ,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,UAAU,EACV,UAAU,EACV,UAAU,EACV,WAAW,EACX,KAAK,EACL,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,SAAS,EACT,aAAa,GACd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,YAAY,EACV,WAAW,EACX,IAAI,EACJ,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,SAAS,GACV,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,kBAAkB,EAClB,cAAc,GACf,MAAM,uBAAuB,CAAC;AAE/B,eAAO,MAAM,YAAY,UAAU,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @husk-ai/core -- the contracts.
3
+ *
4
+ * Every other package depends on this one and nothing else in the workspace.
5
+ * If a type belongs to more than one package, it belongs here.
6
+ */
7
+ export * from './ids.js';
8
+ export * from './errors.js';
9
+ export * from './logger.js';
10
+ export * from './util.js';
11
+ export * from './lifecycle.js';
12
+ export * from './audit.js';
13
+ export * from './config.js';
14
+ export * from './spec.js';
15
+ export * from './net.js';
16
+ export * from './browse.js';
17
+ export { messageText } from './types/model.js';
18
+ export const HUSK_VERSION = '0.1.0';
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAyC5B,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAsB/C,MAAM,CAAC,MAAM,YAAY,GAAG,OAAO,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Things that must be torn down when a computer goes away.
3
+ *
4
+ * The leak this exists to close: a browser session outlives its computer.
5
+ * `@husk-ai/browser` keeps a Chromium running inside the machine and a `pkill` in
6
+ * `close()` to stop it -- but nothing called `close()` when the computer was
7
+ * destroyed. Only `DELETE /v1/computers/:id/browser` did, which is a route
8
+ * almost nobody calls. So every machine that ever opened a page left a browser
9
+ * running: measured, 23 `headless_shell` processes still alive the next day,
10
+ * each holding its workspace directory open so `husk rm` could not delete it
11
+ * and ~325 MB per machine stayed on disk.
12
+ *
13
+ * A registry rather than a direct call because of layering. `@husk-ai/runtime`
14
+ * owns the lifecycle and must not import `@husk-ai/browser` -- the browser is an
15
+ * optional thing built *on* computers, and a runtime that depended on it would
16
+ * drag Chromium provisioning into every install. So the runtime announces, and
17
+ * whoever cares subscribes.
18
+ */
19
+ export type DestroyHook = (computerId: string) => void | Promise<void>;
20
+ /**
21
+ * Run `fn` when any computer is destroyed. Returns an unsubscribe.
22
+ *
23
+ * Register at module load in the package that owns the resource, so the hook is
24
+ * in place before anything could have created one.
25
+ */
26
+ export declare function onComputerDestroyed(fn: DestroyHook): () => void;
27
+ /**
28
+ * Announce a destroyed computer and wait for every subscriber.
29
+ *
30
+ * Failures are swallowed deliberately: this runs on the teardown path, and a
31
+ * cleanup that throws must not stop the destroy it is cleaning up after, nor
32
+ * stop the other subscribers. A browser that cannot be closed is a smaller
33
+ * problem than a computer that cannot be removed.
34
+ */
35
+ export declare function notifyComputerDestroyed(computerId: string): Promise<void>;
36
+ /** Test seam. Not for production use -- hooks are registered at module load. */
37
+ export declare function clearDestroyHooks(): void;
38
+ //# sourceMappingURL=lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAIvE;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,WAAW,GAAG,MAAM,IAAI,CAG/D;AAED;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAU/E;AAED,gFAAgF;AAChF,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Things that must be torn down when a computer goes away.
3
+ *
4
+ * The leak this exists to close: a browser session outlives its computer.
5
+ * `@husk-ai/browser` keeps a Chromium running inside the machine and a `pkill` in
6
+ * `close()` to stop it -- but nothing called `close()` when the computer was
7
+ * destroyed. Only `DELETE /v1/computers/:id/browser` did, which is a route
8
+ * almost nobody calls. So every machine that ever opened a page left a browser
9
+ * running: measured, 23 `headless_shell` processes still alive the next day,
10
+ * each holding its workspace directory open so `husk rm` could not delete it
11
+ * and ~325 MB per machine stayed on disk.
12
+ *
13
+ * A registry rather than a direct call because of layering. `@husk-ai/runtime`
14
+ * owns the lifecycle and must not import `@husk-ai/browser` -- the browser is an
15
+ * optional thing built *on* computers, and a runtime that depended on it would
16
+ * drag Chromium provisioning into every install. So the runtime announces, and
17
+ * whoever cares subscribes.
18
+ */
19
+ const hooks = new Set();
20
+ /**
21
+ * Run `fn` when any computer is destroyed. Returns an unsubscribe.
22
+ *
23
+ * Register at module load in the package that owns the resource, so the hook is
24
+ * in place before anything could have created one.
25
+ */
26
+ export function onComputerDestroyed(fn) {
27
+ hooks.add(fn);
28
+ return () => hooks.delete(fn);
29
+ }
30
+ /**
31
+ * Announce a destroyed computer and wait for every subscriber.
32
+ *
33
+ * Failures are swallowed deliberately: this runs on the teardown path, and a
34
+ * cleanup that throws must not stop the destroy it is cleaning up after, nor
35
+ * stop the other subscribers. A browser that cannot be closed is a smaller
36
+ * problem than a computer that cannot be removed.
37
+ */
38
+ export async function notifyComputerDestroyed(computerId) {
39
+ await Promise.all([...hooks].map(async (fn) => {
40
+ try {
41
+ await fn(computerId);
42
+ }
43
+ catch {
44
+ // teardown is best-effort by construction
45
+ }
46
+ }));
47
+ }
48
+ /** Test seam. Not for production use -- hooks are registered at module load. */
49
+ export function clearDestroyHooks() {
50
+ hooks.clear();
51
+ }
52
+ //# sourceMappingURL=lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,MAAM,KAAK,GAAG,IAAI,GAAG,EAAe,CAAC;AAErC;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAAe;IACjD,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACd,OAAO,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AAChC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,UAAkB;IAC9D,MAAM,OAAO,CAAC,GAAG,CACf,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;QAC1B,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,UAAU,CAAC,CAAC;QACvB,CAAC;QAAC,MAAM,CAAC;YACP,0CAA0C;QAC5C,CAAC;IACH,CAAC,CAAC,CACH,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,iBAAiB;IAC/B,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC"}
@@ -0,0 +1,20 @@
1
+ export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'silent';
2
+ export interface Logger {
3
+ level: LogLevel;
4
+ trace(msg: string, meta?: unknown): void;
5
+ debug(msg: string, meta?: unknown): void;
6
+ info(msg: string, meta?: unknown): void;
7
+ warn(msg: string, meta?: unknown): void;
8
+ error(msg: string, meta?: unknown): void;
9
+ child(scope: string): Logger;
10
+ }
11
+ export interface LoggerOptions {
12
+ level?: LogLevel;
13
+ scope?: string;
14
+ /** Emit newline-delimited JSON instead of pretty text. HUSK_LOG_JSON=1 forces it. */
15
+ json?: boolean;
16
+ sink?: (line: string) => void;
17
+ }
18
+ export declare function createLogger(opts?: LoggerOptions): Logger;
19
+ export declare const log: Logger;
20
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAIhF,MAAM,WAAW,MAAM;IACrB,KAAK,EAAE,QAAQ,CAAC;IAChB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACzC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACzC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACxC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACxC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACzC,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qFAAqF;IACrF,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B;AAaD,wBAAgB,YAAY,CAAC,IAAI,GAAE,aAAkB,GAAG,MAAM,CAuC7D;AAUD,eAAO,MAAM,GAAG,QAAkC,CAAC"}
package/dist/logger.js ADDED
@@ -0,0 +1,59 @@
1
+ const ORDER = { trace: 10, debug: 20, info: 30, warn: 40, error: 50, silent: 99 };
2
+ const CSI = String.fromCharCode(27) + '[';
3
+ const RESET = CSI + '0m';
4
+ const DIM = CSI + '2m';
5
+ const COLOR = {
6
+ trace: CSI + '90m',
7
+ debug: CSI + '36m',
8
+ info: CSI + '32m',
9
+ warn: CSI + '33m',
10
+ error: CSI + '31m',
11
+ };
12
+ export function createLogger(opts = {}) {
13
+ const envLevel = process.env.HUSK_LOG_LEVEL;
14
+ const json = opts.json ?? process.env.HUSK_LOG_JSON === '1';
15
+ const sink = opts.sink ?? ((line) => process.stderr.write(line + '\n'));
16
+ const state = { level: opts.level ?? envLevel ?? 'info', scope: opts.scope ?? '' };
17
+ const useColor = !json && process.stderr.isTTY === true && !process.env.NO_COLOR;
18
+ function emit(level, msg, meta) {
19
+ if (ORDER[level] < ORDER[state.level])
20
+ return;
21
+ if (json) {
22
+ sink(JSON.stringify({ t: new Date().toISOString(), level, scope: state.scope || undefined, msg, meta }));
23
+ return;
24
+ }
25
+ const tag = useColor ? COLOR[level] + level.padEnd(5) + RESET : level.padEnd(5);
26
+ const scope = state.scope ? (useColor ? `${DIM}[${state.scope}]${RESET} ` : `[${state.scope}] `) : '';
27
+ let line = `${tag} ${scope}${msg}`;
28
+ if (meta !== undefined) {
29
+ const s = typeof meta === 'string' ? meta : safeStringify(meta);
30
+ line += useColor ? ` ${DIM}${s}${RESET}` : ` ${s}`;
31
+ }
32
+ sink(line);
33
+ }
34
+ const logger = {
35
+ get level() {
36
+ return state.level;
37
+ },
38
+ set level(l) {
39
+ state.level = l;
40
+ },
41
+ trace: (m, x) => emit('trace', m, x),
42
+ debug: (m, x) => emit('debug', m, x),
43
+ info: (m, x) => emit('info', m, x),
44
+ warn: (m, x) => emit('warn', m, x),
45
+ error: (m, x) => emit('error', m, x),
46
+ child: (scope) => createLogger({ ...opts, level: state.level, scope: state.scope ? `${state.scope}:${scope}` : scope }),
47
+ };
48
+ return logger;
49
+ }
50
+ function safeStringify(v) {
51
+ try {
52
+ return JSON.stringify(v, (_k, val) => (val instanceof Error ? { name: val.name, message: val.message } : val)) ?? '';
53
+ }
54
+ catch {
55
+ return String(v);
56
+ }
57
+ }
58
+ export const log = createLogger({ scope: 'husk' });
59
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"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"}
package/dist/net.d.ts ADDED
@@ -0,0 +1,48 @@
1
+ import type { NetworkPolicy } from './types/computer.js';
2
+ /**
3
+ * Network policy, in one place.
4
+ *
5
+ * This lived in three copies -- `@husk-ai/runtime`, `@husk-ai/agent`, and by
6
+ * implication the docs -- and they drifted: one blocked the cloud metadata
7
+ * endpoint in `mode: 'full'` and one did not. A security check with two answers
8
+ * is not a security check, so it lives here and everything re-exports it.
9
+ */
10
+ /** Match a hostname against one pattern. Supports a single leading `*.` wildcard. */
11
+ export declare function hostMatches(host: string, pattern: string): boolean;
12
+ export declare function isInternalHost(host: string): boolean;
13
+ /** Loopback specifically, as distinct from the rest of the private ranges. */
14
+ export declare function isLoopbackHost(host: string): boolean;
15
+ /** Whether this provider's loopback belongs to the computer rather than the host. */
16
+ export declare function ownsLoopback(provider: string): boolean;
17
+ /**
18
+ * Whether the computer shares a network stack with whoever is running husk.
19
+ *
20
+ * The inverse of {@link ownsLoopback}, named for the thing a caller usually
21
+ * wants to warn about.
22
+ */
23
+ export declare function sharesHostNetwork(provider: string): boolean;
24
+ export interface HostPolicyContext {
25
+ /**
26
+ * True when this computer's loopback is its own, not the host's.
27
+ *
28
+ * The whole point of the loopback rule is to stop an agent reaching the
29
+ * *host's* services. On docker, podman and fly, `127.0.0.1` inside the
30
+ * computer is a separate network namespace, so "start a dev server and look
31
+ * at it" -- the single most common thing an agent does -- is both safe and
32
+ * necessary. On the `local` provider loopback is literally the host's, and on
33
+ * `ssh` it is a box the user may well be running things on, so both stay shut
34
+ * unless the operator names the host in `allow`.
35
+ */
36
+ loopbackIsOwn?: boolean;
37
+ }
38
+ export declare function isHostAllowed(host: string, policy: NetworkPolicy | undefined, ctx?: HostPolicyContext): boolean;
39
+ /** The hostname of a URL, or null when it does not parse. */
40
+ export declare function urlHost(url: string): string | null;
41
+ /**
42
+ * Parse and authorise a URL in one step.
43
+ *
44
+ * Returns the parsed URL so a caller cannot accidentally check one string and
45
+ * then fetch a different one.
46
+ */
47
+ export declare function assertUrlAllowed(url: string, policy: NetworkPolicy | undefined, ctx?: HostPolicyContext): URL;
48
+ //# sourceMappingURL=net.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"net.d.ts","sourceRoot":"","sources":["../src/net.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;;;;;GAOG;AAEH,qFAAqF;AACrF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CASlE;AAuDD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAmBpD;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMpD;AAiBD,qFAAqF;AACrF,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED,MAAM,WAAW,iBAAiB;IAChC;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,aAAa,GAAG,SAAS,EACjC,GAAG,GAAE,iBAAsB,GAC1B,OAAO,CA0BT;AAED,6DAA6D;AAC7D,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMlD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,aAAa,GAAG,SAAS,EACjC,GAAG,GAAE,iBAAsB,GAC1B,GAAG,CAiCL"}