mahler
Version:
A automated task composer and HTN based planner for building autonomous system agents
201 lines • 6.75 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Task = void 0;
const crypto_1 = require("crypto");
const assert_1 = require("../assert");
const lens_1 = require("../lens");
const path_1 = require("../path");
const view_1 = require("../view");
const context_1 = require("./context");
const instructions_1 = require("./instructions");
// Bind a task to a specific context
function ground(task, args) {
const templateParts = path_1.Path.split(task.lens);
// Form the context path from the task lens and the
// given task arguments
const path = path_1.Path.from(templateParts.map((p) => {
if (p.startsWith(':')) {
const key = p.slice(1);
(0, assert_1.default)(key in args, `Missing parameter '${key}' in context given to task '${task.id}', required by lens '${task.lens}'`);
return String(args[key]);
}
return p;
}));
// We clone the target before passing it to the task as an action
// could assign it to the state and modify it and we don't want actions
// from methods altering the target from the parent method
const target = structuredClone(args.target);
const lensCtx = lens_1.Lens.context(task.lens, path, target);
const context = context_1.Context.from(lensCtx);
const { id, description: taskDescription } = task;
const description = typeof taskDescription === 'function'
? taskDescription(context)
: taskDescription;
const condition = (s) => {
const lens = lens_1.Lens.from(s, path);
return task.condition(lens, {
...context,
system: s,
});
};
if (isActionTask(task)) {
const { effect: taskEffect, action: taskAction } = task;
const effect = (s) => {
taskEffect(view_1.View.from(s, path), { ...context, system: s._ });
};
const action = (s) => taskAction(view_1.View.from(s, path), { ...context, system: s._ });
return Object.assign(action, {
id,
path: context.path,
target,
_tag: 'action',
description,
condition,
effect,
toJSON() {
return {
id,
path: context.path,
description,
target: args.target,
};
},
});
}
const { expansion } = task;
const method = (s) => task.method(lens_1.Lens.from(s, context.path), {
...context,
system: s,
});
return Object.assign(method, {
id,
path: context.path,
target,
_tag: 'method',
description,
condition,
expansion,
toJSON() {
return {
id,
path: context.path,
description,
target: args.target,
};
},
});
}
/**
* Check if a task is a method
*/
function isMethodTask(t) {
return t.method != null && typeof t.method === 'function';
}
/**
* Check if a task or an instruction is an action
*/
function isActionTask(t) {
return (t.effect != null &&
typeof t.effect === 'function' &&
t.action != null &&
typeof t.action === 'function');
}
// We put this here instead of props so we don't export it externally when doing
// export * from props
function isActionProps(x) {
return (typeof x.effect === 'function' ||
typeof x.action === 'function');
}
function serialize(props) {
// Serialize the task specification converting all elements to strings
// including the function bodys where it applies
const serialized = Object.keys(props).reduce((o, k) => ({ ...o, [k]: String(props[k]) }), {});
return (0, crypto_1.createHash)('sha256').update(JSON.stringify(serialized)).digest('hex');
}
function from(taskProps) {
const { op = 'update', condition: taskCondition = () => true } = taskProps;
// Check that the path is valid
const lens = path_1.Path.from(taskProps.lens ?? '/');
// Generate a deterministic id for
// the task. This is useful for diagramming
const id = serialize(taskProps);
const opLabel = op === '*' ? 'modify' : op;
// The default description is
// update /a/b/c or
// [method] update /a/b/c
const description = (ctx) => `${opLabel} ${ctx.path}`;
// Create operations require that the sub-element pointed by the value
// does not exist yet
let condition = taskCondition;
if (op === 'create') {
condition = (v, c) => v === undefined && taskCondition(v, c);
}
// Delete and update operations require that the sub-element pointed by the value
// exists
if (['delete', 'update'].includes(op)) {
condition = (v, c) => v !== undefined && taskCondition(v, c);
}
// The task properties
const tProps = (() => {
if (isActionProps(taskProps)) {
const { effect: taskEffect = () => void 0, action: taskAction = (v, c) => {
taskEffect(v, c);
// The action function needs to return a promise
// but taskEffect returns `void`
return Promise.resolve();
}, } = taskProps;
let effect = taskEffect;
let action = taskAction;
if (op === 'delete') {
// If the task defines a delete operation for the value pointed by
// the lens, then we need to delete the property after the action succeeds
effect = (v, c) => {
taskEffect(v, c);
v.delete();
};
action = async (v, c) => {
await taskAction(v, c);
v.delete();
};
}
return {
description,
op: op,
...taskProps,
lens,
condition,
effect,
action,
};
}
else {
return {
description,
op: op,
expansion: instructions_1.MethodExpansion.DETECT,
...taskProps,
lens,
condition,
};
}
})();
// The final task spec. Typescript doesn't seem to
// correctly infer the type here unfortunately
const tSpec = { id, ...tProps };
const t = Object.assign((ctx) => {
return ground(tSpec, ctx);
}, tSpec);
return t;
}
function of() {
return {
from,
};
}
exports.Task = {
of,
from,
isMethod: isMethodTask,
isAction: isActionTask,
};
//# sourceMappingURL=tasks.js.map