diff --git a/actions.go b/actions.go index d5514ec..6686456 100644 --- a/actions.go +++ b/actions.go @@ -4,6 +4,29 @@ import "time" // --- ACTION messages (broadcast via Core.ACTION) --- +// TaskProcessStart requests asynchronous process execution through Core.PERFORM. +// The handler returns a snapshot of the started process immediately. +// +// Example: +// +// c.PERFORM(process.TaskProcessStart{Command: "sleep", Args: []string{"10"}}) +type TaskProcessStart struct { + Command string + Args []string + Dir string + Env []string + // DisableCapture skips buffering process output before returning it. + DisableCapture bool + // Detach runs the command in its own process group. + Detach bool + // Timeout bounds the execution duration. + Timeout time.Duration + // GracePeriod controls SIGTERM-to-SIGKILL escalation. + GracePeriod time.Duration + // KillGroup terminates the entire process group instead of only the leader. + KillGroup bool +} + // TaskProcessRun requests synchronous command execution through Core.PERFORM. // The handler returns the combined command output on success. // diff --git a/service.go b/service.go index a426e01..21d8c0b 100644 --- a/service.go +++ b/service.go @@ -555,6 +555,22 @@ func (s *Service) RunWithOptions(ctx context.Context, opts RunOptions) (string, // handleTask dispatches Core.PERFORM messages for the process service. func (s *Service) handleTask(c *core.Core, task core.Task) core.Result { switch m := task.(type) { + case TaskProcessStart: + proc, err := s.StartWithOptions(c.Context(), RunOptions{ + Command: m.Command, + Args: m.Args, + Dir: m.Dir, + Env: m.Env, + DisableCapture: m.DisableCapture, + Detach: m.Detach, + Timeout: m.Timeout, + GracePeriod: m.GracePeriod, + KillGroup: m.KillGroup, + }) + if err != nil { + return core.Result{Value: err, OK: false} + } + return core.Result{Value: proc.Info(), OK: true} case TaskProcessRun: output, err := s.RunWithOptions(c.Context(), RunOptions{ Command: m.Command, diff --git a/service_test.go b/service_test.go index e26cfc3..bd381ed 100644 --- a/service_test.go +++ b/service_test.go @@ -596,6 +596,33 @@ func TestService_OnShutdown(t *testing.T) { } func TestService_OnStartup(t *testing.T) { + t.Run("registers process.start task", func(t *testing.T) { + svc, c := newTestService(t) + + err := svc.OnStartup(context.Background()) + require.NoError(t, err) + + result := c.PERFORM(TaskProcessStart{ + Command: "sleep", + Args: []string{"1"}, + }) + + require.True(t, result.OK) + + info, ok := result.Value.(Info) + require.True(t, ok) + assert.NotEmpty(t, info.ID) + assert.Equal(t, StatusRunning, info.Status) + assert.True(t, info.Running) + + proc, err := svc.Get(info.ID) + require.NoError(t, err) + assert.True(t, proc.IsRunning()) + + <-proc.Done() + assert.Equal(t, StatusExited, proc.Status) + }) + t.Run("registers process.run task", func(t *testing.T) { svc, c := newTestService(t)